├── .gitattributes
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── composer.json
├── phploc
├── phpunit9.xml
└── src
├── Analyser.php
├── CLI
├── Application.php
├── Arguments.php
└── ArgumentsBuilder.php
├── Collector.php
├── Exception
├── ArgumentsBuilderException.php
├── Exception.php
└── RuntimeException.php
├── Log
├── Csv.php
├── Json.php
├── Text.php
└── Xml.php
└── Publisher.php
/.gitattributes:
--------------------------------------------------------------------------------
1 | /.github export-ignore
2 | /.phive export-ignore
3 | /.psalm export-ignore
4 | /.php-cs-fixer.dist.php export-ignore
5 | /build export-ignore
6 | /build.xml export-ignore
7 | /phive.xml export-ignore
8 | /phpunit.xml export-ignore
9 | /tests export-ignore
10 | /tools export-ignore
11 | /tools/* binary
12 |
13 | *.php diff=php
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /build/phar
2 | /build/*.phar*
3 | /.idea
4 | /composer.lock
5 | /vendor
6 | /.php-cs-fixer.php
7 | /.php-cs-fixer.cache
8 | /.phpunit.cache
9 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changes in PHPLOC
2 |
3 | All notable changes in PHPLOC are documented in this file using the [Keep a CHANGELOG](http://keepachangelog.com/) principles.
4 |
5 | ## [8.0.6] - 2025-03-29
6 |
7 | ### Changed
8 |
9 | * Bump dependencies
10 |
11 | ## [8.0.5] - 2025-03-29
12 |
13 | ### Changed
14 |
15 | * Bump dependencies
16 |
17 | ## [8.0.4] - 2024-10-31
18 |
19 | ### Changed
20 |
21 | * Bump dependencies
22 |
23 | ## [8.0.3] - 2023-08-05
24 |
25 | ### Changed
26 |
27 | * rm use of version dependency, fix php-file-iterator version handling
28 |
29 | ## [8.0.2] - 2023-03-19
30 |
31 | ### Changed
32 |
33 | * Expanded dependencies to handle both PHPUnit 9 and 10.
34 |
35 | ## [8.0.1] - 2023-03-17
36 |
37 | ### Changed
38 |
39 | * Changed PHP version constraint in `composer.json` from `^8.1` to `^7.4 || ^8.0`
40 |
41 | ## [8.0.0] - 2023-02-23
42 |
43 | ### Changed
44 |
45 | * Changed PHP version constraint in `composer.json` from `>=7.3` to `^8.1`
46 | * Changed Namespace from `SebastianBergmann\PHPLOC` to `Cmgmyr\PHPLOC`
47 | * Removed tools and phar build files, added GitHub actions
48 |
49 | ## [7.0.2] - 2020-12-07
50 |
51 | ### Changed
52 |
53 | * Changed PHP version constraint in `composer.json` from `^7.3` to `>=7.3`
54 |
55 | ## [7.0.1] - 2020-08-18
56 |
57 | ### Fixed
58 |
59 | * The `--exclude` CLI option was not handled correctly
60 |
61 | ## [7.0.0] - 2020-08-13
62 |
63 | ### Removed
64 |
65 | * The `--names` CLI option has been removed; use the `--suffix` CLI option instead
66 | * The `--names-exclude` CLI option has been removed; use the `--exclude` CLI option instead
67 |
68 | ## [6.0.2] - 2020-02-28
69 |
70 | ### Fixed
71 |
72 | * [#207](https://github.com/sebastianbergmann/phploc/issues/207): `TypeError` in `DOMDocument::createElement()` calls
73 |
74 | ## [6.0.1] - 2020-02-27
75 |
76 | ### Fixed
77 |
78 | * [#205](https://github.com/sebastianbergmann/phploc/pull/205): `TypeError` in `ini_set()` calls
79 |
80 | ## [6.0.0] - 2020-02-20
81 |
82 | ### Removed
83 |
84 | * This tool is no longer supported on PHP 7.2
85 |
86 | ## [5.0.0] - 2019-03-16
87 |
88 | ### Fixed
89 |
90 | * [#182](https://github.com/sebastianbergmann/phploc/pull/182): `"continue" targeting switch is equivalent to "break". Did you mean to use "continue 2"`
91 |
92 | ### Removed
93 |
94 | * This tool is no longer supported on PHP 5.6, PHP 7.0, and PHP 7.1
95 |
96 | ## [4.0.1] - 2017-11-18
97 |
98 | ### Changed
99 |
100 | * This tool is now compatible with Symfony Console 4
101 |
102 | ## [4.0.0] - 2017-06-06
103 |
104 | ### Removed
105 |
106 | * Removed the `--git-repository` option (and the corresponding functionality)
107 | * Removed the `--progress` option (and the corresponding functionality)
108 |
109 | ## [3.0.1] - 2016-04-25
110 |
111 | ### Fixed
112 |
113 | * [#139](https://github.com/sebastianbergmann/phploc/issues/139): Introduction of `T_USE` in `Analyser.php` gives `PHP Notice: Undefined index: ccn`
114 | * [#141](https://github.com/sebastianbergmann/phploc/issues/141): `Undefined index: ccn in phar:///usr/local/bin/phploc/src/Analyser.php on line 507`
115 |
116 | ### Fixed
117 |
118 | ## [3.0.0] - 2016-01-13
119 |
120 | [7.0.2]: https://github.com/sebastianbergmann/phploc/compare/7.0.1...7.0.2
121 | [7.0.1]: https://github.com/sebastianbergmann/phploc/compare/7.0.0...7.0.1
122 | [7.0.0]: https://github.com/sebastianbergmann/phploc/compare/6.0.2...7.0.0
123 | [6.0.2]: https://github.com/sebastianbergmann/phploc/compare/6.0.1...6.0.2
124 | [6.0.1]: https://github.com/sebastianbergmann/phploc/compare/6.0.0...6.0.1
125 | [6.0.0]: https://github.com/sebastianbergmann/phploc/compare/5.0.0...6.0.0
126 | [5.0.0]: https://github.com/sebastianbergmann/phploc/compare/4.0.1...5.0.0
127 | [4.0.1]: https://github.com/sebastianbergmann/phploc/compare/4.0.0...4.0.1
128 | [4.0.0]: https://github.com/sebastianbergmann/phploc/compare/3.0...4.0.0
129 | [3.0.1]: https://github.com/sebastianbergmann/phploc/compare/3.0.0...3.0.1
130 | [3.0.0]: https://github.com/sebastianbergmann/phploc/compare/2.1.5...3.0.0
131 |
132 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 3-Clause License
2 |
3 | Copyright (c) 2023, Chris Gmyr
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | 1. Redistributions of source code must retain the above copyright notice, this
10 | list of conditions and the following disclaimer.
11 |
12 | 2. Redistributions in binary form must reproduce the above copyright notice,
13 | this list of conditions and the following disclaimer in the documentation
14 | and/or other materials provided with the distribution.
15 |
16 | 3. Neither the name of the copyright holder nor the names of its
17 | contributors may be used to endorse or promote products derived from
18 | this software without specific prior written permission.
19 |
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PHPLOC
2 |
3 | `phploc` is a tool for quickly measuring the size and analyzing the structure of a PHP project.
4 |
5 | ## Installation
6 |
7 | ```bash
8 | $ composer global install cmgmyr/phploc
9 | ```
10 | ## Usage Examples
11 |
12 | ### Analyse a directory and print the result
13 |
14 | ```
15 | $ phploc src
16 | phploc 8.0.6 by Chris Gmyr.
17 |
18 | Directories 3
19 | Files 10
20 |
21 | Size
22 | Lines of Code (LOC) 1882
23 | Comment Lines of Code (CLOC) 255 (13.55%)
24 | Non-Comment Lines of Code (NCLOC) 1627 (86.45%)
25 | Logical Lines of Code (LLOC) 377 (20.03%)
26 | Classes 351 (93.10%)
27 | Average Class Length 35
28 | Minimum Class Length 0
29 | Maximum Class Length 172
30 | Average Method Length 2
31 | Minimum Method Length 1
32 | Maximum Method Length 117
33 | Functions 0 (0.00%)
34 | Average Function Length 0
35 | Not in classes or functions 26 (6.90%)
36 |
37 | Cyclomatic Complexity
38 | Average Complexity per LLOC 0.49
39 | Average Complexity per Class 19.60
40 | Minimum Class Complexity 1.00
41 | Maximum Class Complexity 139.00
42 | Average Complexity per Method 2.43
43 | Minimum Method Complexity 1.00
44 | Maximum Method Complexity 96.00
45 |
46 | Dependencies
47 | Global Accesses 0
48 | Global Constants 0 (0.00%)
49 | Global Variables 0 (0.00%)
50 | Super-Global Variables 0 (0.00%)
51 | Attribute Accesses 85
52 | Non-Static 85 (100.00%)
53 | Static 0 (0.00%)
54 | Method Calls 280
55 | Non-Static 276 (98.57%)
56 | Static 4 (1.43%)
57 |
58 | Structure
59 | Namespaces 3
60 | Interfaces 1
61 | Traits 0
62 | Classes 9
63 | Abstract Classes 0 (0.00%)
64 | Concrete Classes 9 (100.00%)
65 | Methods 130
66 | Scope
67 | Non-Static Methods 130 (100.00%)
68 | Static Methods 0 (0.00%)
69 | Visibility
70 | Public Methods 103 (79.23%)
71 | Non-Public Methods 27 (20.77%)
72 | Functions 0
73 | Named Functions 0 (0.00%)
74 | Anonymous Functions 0 (0.00%)
75 | Constants 0
76 | Global Constants 0 (0.00%)
77 | Class Constants 0 (0.00%)
78 | ```
79 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cmgmyr/phploc",
3 | "description": "A tool for quickly measuring the size of a PHP project.",
4 | "homepage": "https://github.com/cmgmyr/phploc",
5 | "license": "BSD-3-Clause",
6 | "authors": [
7 | {
8 | "name": "Chris Gmyr",
9 | "email": "cmgmyr@gmail.com",
10 | "role": "lead"
11 | }
12 | ],
13 | "support": {
14 | "issues": "https://github.com/cmgmyr/phploc/issues"
15 | },
16 | "config": {
17 | "optimize-autoloader": true,
18 | "sort-packages": true
19 | },
20 | "require": {
21 | "php": "^7.4 || ^8.0",
22 | "ext-dom": "*",
23 | "ext-json": "*",
24 | "sebastian/cli-parser": "^1.0|^2.0|^3.0|^4.0",
25 | "phpunit/php-file-iterator": "^3.0|^4.0|^5.0|^6.0"
26 | },
27 | "require-dev": {
28 | "friendsofphp/php-cs-fixer": "^3.2",
29 | "phpunit/phpunit": "^9.0|^10.0",
30 | "vimeo/psalm": "^5.7"
31 | },
32 | "autoload": {
33 | "classmap": [
34 | "src/"
35 | ]
36 | },
37 | "bin": [
38 | "phploc"
39 | ],
40 | "extra": {
41 | "branch-alias": {
42 | "dev-main": "8.0-dev"
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/phploc:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | $loaded = false;
13 |
14 | foreach (array(__DIR__ . '/../../autoload.php', __DIR__ . '/vendor/autoload.php') as $file) {
15 | if (file_exists($file)) {
16 | require $file;
17 | $loaded = true;
18 | break;
19 | }
20 | }
21 |
22 | if (!$loaded) {
23 | die(
24 | 'You need to set up the project dependencies using the following commands:' . PHP_EOL .
25 | 'wget http://getcomposer.org/composer.phar' . PHP_EOL .
26 | 'php composer.phar install' . PHP_EOL
27 | );
28 | }
29 |
30 | exit((new \Cmgmyr\PHPLOC\Application)->run($_SERVER['argv']));
31 |
--------------------------------------------------------------------------------
/phpunit9.xml:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 | tests
17 |
18 |
19 |
20 |
22 |
23 | src
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/Analyser.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 | namespace Cmgmyr\PHPLOC;
11 |
12 | use const T_ABSTRACT;
13 | use const T_BOOLEAN_AND;
14 | use const T_BOOLEAN_OR;
15 | use const T_CASE;
16 | use const T_CATCH;
17 | use const T_CLASS;
18 | use const T_COMMENT;
19 | use const T_CONST;
20 | use const T_CONSTANT_ENCAPSED_STRING;
21 | use const T_CURLY_OPEN;
22 | use const T_DECLARE;
23 | use const T_DOC_COMMENT;
24 | use const T_DOLLAR_OPEN_CURLY_BRACES;
25 | use const T_DOUBLE_COLON;
26 | use const T_ELSEIF;
27 | use const T_EXTENDS;
28 | use const T_FINAL;
29 | use const T_FOR;
30 | use const T_FOREACH;
31 | use const T_FUNCTION;
32 | use const T_GLOBAL;
33 | use const T_IF;
34 | use const T_INTERFACE;
35 | use const T_LOGICAL_AND;
36 | use const T_LOGICAL_OR;
37 | use const T_NAMESPACE;
38 | use const T_NEW;
39 | use const T_NS_SEPARATOR;
40 | use const T_OBJECT_OPERATOR;
41 | use const T_PRIVATE;
42 | use const T_PROTECTED;
43 | use const T_PUBLIC;
44 | use const T_STATIC;
45 | use const T_STRING;
46 | use const T_TRAIT;
47 | use const T_USE;
48 | use const T_VARIABLE;
49 | use const T_WHILE;
50 | use const T_WHITESPACE;
51 | use function array_pop;
52 | use function count;
53 | use function file_get_contents;
54 | use function in_array;
55 | use function is_array;
56 | use function is_string;
57 | use function rtrim;
58 | use function str_replace;
59 | use function strpos;
60 | use function strtolower;
61 | use function substr;
62 | use function substr_count;
63 | use function token_get_all;
64 | use function trim;
65 |
66 | final class Analyser
67 | {
68 | /**
69 | * @var Collector
70 | */
71 | private $collector;
72 |
73 | /**
74 | * @var array
75 | */
76 | private $classes = [];
77 |
78 | /**
79 | * @var array
80 | */
81 | private $superGlobals = [
82 | '$_ENV' => true,
83 | '$_POST' => true,
84 | '$_GET' => true,
85 | '$_COOKIE' => true,
86 | '$_SERVER' => true,
87 | '$_FILES' => true,
88 | '$_REQUEST' => true,
89 | '$HTTP_ENV_VARS' => true,
90 | '$HTTP_POST_VARS' => true,
91 | '$HTTP_GET_VARS' => true,
92 | '$HTTP_COOKIE_VARS' => true,
93 | '$HTTP_SERVER_VARS' => true,
94 | '$HTTP_POST_FILES' => true,
95 | ];
96 |
97 | public function __construct()
98 | {
99 | $this->collector = new Collector;
100 | }
101 |
102 | public function countFiles(array $files, bool $countTests)
103 | {
104 | foreach ($files as $file) {
105 | $this->countFile($file, $countTests);
106 | }
107 |
108 | return $this->collector->getPublisher()->toArray();
109 | }
110 |
111 | public function preProcessFile(string $filename): void
112 | {
113 | $tokens = token_get_all(file_get_contents($filename));
114 | $numTokens = count($tokens);
115 | $namespace = false;
116 |
117 | for ($i = 0; $i < $numTokens; $i++) {
118 | if (is_string($tokens[$i])) {
119 | continue;
120 | }
121 |
122 | switch ($tokens[$i][0]) {
123 | case T_NAMESPACE:
124 | $namespace = $this->getNamespaceName($tokens, $i);
125 |
126 | break;
127 |
128 | case T_CLASS:
129 | if (!$this->isClassDeclaration($tokens, $i)) {
130 | break;
131 | }
132 |
133 | $className = $this->getClassName($namespace, $tokens, $i);
134 |
135 | if (isset($tokens[$i + 4]) && is_array($tokens[$i + 4]) &&
136 | $tokens[$i + 4][0] === T_EXTENDS) {
137 | $parent = $this->getClassName($namespace, $tokens, $i + 4);
138 | } else {
139 | $parent = null;
140 | }
141 |
142 | $this->classes[$className] = $parent;
143 |
144 | break;
145 | }
146 | }
147 | }
148 |
149 | /**
150 | * Processes a single file.
151 | *
152 | * @param string $filename
153 | * @param bool $countTests
154 | */
155 | public function countFile($filename, $countTests): void
156 | {
157 | if ($countTests) {
158 | $this->preProcessFile($filename);
159 | }
160 |
161 | $buffer = file_get_contents($filename);
162 | $this->collector->incrementLines(substr_count($buffer, "\n"));
163 | $tokens = token_get_all($buffer);
164 | $numTokens = count($tokens);
165 |
166 | unset($buffer);
167 |
168 | $this->collector->addFile($filename);
169 |
170 | $blocks = [];
171 | $currentBlock = false;
172 | $namespace = false;
173 | $className = null;
174 | $functionName = null;
175 | $testClass = false;
176 | $this->collector->currentClassReset();
177 | $isLogicalLine = true;
178 | $isInMethod = false;
179 |
180 | for ($i = 0; $i < $numTokens; $i++) {
181 | if (is_string($tokens[$i])) {
182 | $token = trim($tokens[$i]);
183 |
184 | if ($token === ';') {
185 | if ($isLogicalLine) {
186 | if ($className !== null && !$testClass) {
187 | $this->collector->currentClassIncrementLines();
188 |
189 | if ($functionName !== null) {
190 | $this->collector->currentMethodIncrementLines();
191 | }
192 | } elseif ($functionName !== null) {
193 | $this->collector->incrementFunctionLines();
194 | }
195 |
196 | $this->collector->incrementLogicalLines();
197 | }
198 | $isLogicalLine = true;
199 | } elseif ($token === '?' && !$testClass) {
200 | if ($className !== null) {
201 | $this->collector->currentClassIncrementComplexity();
202 | $this->collector->currentMethodIncrementComplexity();
203 | }
204 |
205 | $this->collector->incrementComplexity();
206 | } elseif ($token === '{') {
207 | if ($currentBlock === T_CLASS) {
208 | $block = $className;
209 | } elseif ($currentBlock === T_FUNCTION) {
210 | $block = $functionName;
211 | } else {
212 | $block = false;
213 | }
214 |
215 | $blocks[] = $block;
216 |
217 | $currentBlock = false;
218 | } elseif ($token === '}') {
219 | $block = array_pop($blocks);
220 |
221 | if ($block !== false && $block !== null) {
222 | if ($block === $functionName) {
223 | $functionName = null;
224 |
225 | if ($isInMethod) {
226 | $this->collector->currentMethodStop();
227 | $isInMethod = false;
228 | }
229 | } elseif ($block === $className) {
230 | $className = null;
231 | $testClass = false;
232 | $this->collector->currentClassStop();
233 | $this->collector->currentClassReset();
234 | }
235 | }
236 | }
237 |
238 | continue;
239 | }
240 |
241 | [$token, $value] = $tokens[$i];
242 |
243 | switch ($token) {
244 | case T_NAMESPACE:
245 | $namespace = $this->getNamespaceName($tokens, $i);
246 | $this->collector->addNamespace($namespace);
247 | $isLogicalLine = false;
248 |
249 | break;
250 |
251 | case T_CLASS:
252 | case T_INTERFACE:
253 | case T_TRAIT:
254 | if (!$this->isClassDeclaration($tokens, $i)) {
255 | break;
256 | }
257 |
258 | $this->collector->currentClassReset();
259 | $this->collector->currentClassIncrementComplexity();
260 | $className = $this->getClassName($namespace, $tokens, $i);
261 | $currentBlock = T_CLASS;
262 |
263 | if ($token === T_TRAIT) {
264 | $this->collector->incrementTraits();
265 | } elseif ($token === T_INTERFACE) {
266 | $this->collector->incrementInterfaces();
267 | } else {
268 | if ($countTests && $this->isTestClass($className)) {
269 | $testClass = true;
270 | $this->collector->incrementTestClasses();
271 | } else {
272 | $classModifierToken = $this->getPreviousNonWhitespaceNonCommentTokenPos($tokens, $i);
273 |
274 | if ($classModifierToken !== false &&
275 | $tokens[$classModifierToken][0] === T_ABSTRACT
276 | ) {
277 | $this->collector->incrementAbstractClasses();
278 | } elseif (
279 | $classModifierToken !== false &&
280 | $tokens[$classModifierToken][0] === T_FINAL
281 | ) {
282 | $this->collector->incrementFinalClasses();
283 | } else {
284 | $this->collector->incrementNonFinalClasses();
285 | }
286 | }
287 | }
288 |
289 | break;
290 |
291 | case T_FUNCTION:
292 | $prev = $this->getPreviousNonWhitespaceTokenPos($tokens, $i);
293 |
294 | if ($tokens[$prev][0] === T_USE) {
295 | break;
296 | }
297 |
298 | $currentBlock = T_FUNCTION;
299 |
300 | $next = $this->getNextNonWhitespaceTokenPos($tokens, $i);
301 |
302 | if ($tokens[$next] === '&' || (is_array($tokens[$next]) && $tokens[$next][1] === '&')) {
303 | $next = $this->getNextNonWhitespaceTokenPos($tokens, $next);
304 | }
305 |
306 | if (is_array($tokens[$next]) &&
307 | $tokens[$next][0] === T_STRING) {
308 | $functionName = $tokens[$next][1];
309 | } else {
310 | $currentBlock = 'anonymous function';
311 | $functionName = 'anonymous function';
312 | $this->collector->incrementAnonymousFunctions();
313 | }
314 |
315 | if ($currentBlock === T_FUNCTION) {
316 | if ($className === null &&
317 | $functionName !== 'anonymous function') {
318 | $this->collector->incrementNamedFunctions();
319 | } else {
320 | $static = false;
321 | $visibility = T_PUBLIC;
322 |
323 | for ($j = $i; $j > 0; $j--) {
324 | if (is_string($tokens[$j])) {
325 | if ($tokens[$j] === '{' ||
326 | $tokens[$j] === '}' ||
327 | $tokens[$j] === ';') {
328 | break;
329 | }
330 |
331 | continue;
332 | }
333 |
334 | if (isset($tokens[$j][0])) {
335 | switch ($tokens[$j][0]) {
336 | case T_PRIVATE:
337 | $visibility = T_PRIVATE;
338 |
339 | break;
340 |
341 | case T_PROTECTED:
342 | $visibility = T_PROTECTED;
343 |
344 | break;
345 |
346 | case T_STATIC:
347 | $static = true;
348 |
349 | break;
350 | }
351 | }
352 | }
353 |
354 | if ($testClass &&
355 | $this->isTestMethod($functionName, $visibility, $static, $tokens, $i)) {
356 | $this->collector->incrementTestMethods();
357 | } elseif (!$testClass) {
358 | $isInMethod = true;
359 | $this->collector->currentMethodStart();
360 |
361 | $this->collector->currentClassIncrementMethods();
362 |
363 | if (!$static) {
364 | $this->collector->incrementNonStaticMethods();
365 | } else {
366 | $this->collector->incrementStaticMethods();
367 | }
368 |
369 | if ($visibility === T_PUBLIC) {
370 | $this->collector->incrementPublicMethods();
371 | } elseif ($visibility === T_PROTECTED) {
372 | $this->collector->incrementProtectedMethods();
373 | } elseif ($visibility === T_PRIVATE) {
374 | $this->collector->incrementPrivateMethods();
375 | }
376 | }
377 | }
378 | }
379 |
380 | break;
381 |
382 | case T_CURLY_OPEN:
383 | $currentBlock = T_CURLY_OPEN;
384 | $blocks[] = $currentBlock;
385 |
386 | break;
387 |
388 | case T_DOLLAR_OPEN_CURLY_BRACES:
389 | $currentBlock = T_DOLLAR_OPEN_CURLY_BRACES;
390 | $blocks[] = $currentBlock;
391 |
392 | break;
393 |
394 | case T_IF:
395 | case T_ELSEIF:
396 | case T_FOR:
397 | case T_FOREACH:
398 | case T_WHILE:
399 | case T_CASE:
400 | case T_CATCH:
401 | case T_BOOLEAN_AND:
402 | case T_LOGICAL_AND:
403 | case T_BOOLEAN_OR:
404 | case T_LOGICAL_OR:
405 | if (!$testClass) {
406 | if ($isInMethod) {
407 | $this->collector->currentClassIncrementComplexity();
408 | $this->collector->currentMethodIncrementComplexity();
409 | }
410 |
411 | $this->collector->incrementComplexity();
412 | }
413 |
414 | break;
415 |
416 | case T_COMMENT:
417 | case T_DOC_COMMENT:
418 | // We want to count all intermediate lines before the token ends
419 | // But sometimes a new token starts after a newline, we don't want to count that.
420 | // That happened with /* */ and /** */, but not with // since it'll end at the end
421 | $this->collector->incrementCommentLines(substr_count(rtrim($value, "\n"), "\n") + 1);
422 |
423 | break;
424 | case T_CONST:
425 | $possibleScopeToken = $this->getPreviousNonWhitespaceNonCommentTokenPos($tokens, $i);
426 |
427 | if ($possibleScopeToken !== false &&
428 | in_array($tokens[$possibleScopeToken][0], [T_PRIVATE, T_PROTECTED], true)
429 | ) {
430 | $this->collector->incrementNonPublicClassConstants();
431 | } else {
432 | $this->collector->incrementPublicClassConstants();
433 | }
434 |
435 | break;
436 |
437 | case T_STRING:
438 | if ($value === 'define') {
439 | $this->collector->incrementGlobalConstants();
440 |
441 | $j = $i + 1;
442 |
443 | while (isset($tokens[$j]) && $tokens[$j] !== ';') {
444 | if (is_array($tokens[$j]) &&
445 | $tokens[$j][0] === T_CONSTANT_ENCAPSED_STRING) {
446 | $this->collector->addConstant(str_replace('\'', '', $tokens[$j][1]));
447 |
448 | break;
449 | }
450 |
451 | $j++;
452 | }
453 | } else {
454 | $this->collector->addPossibleConstantAccesses($value);
455 | }
456 |
457 | break;
458 |
459 | case T_DOUBLE_COLON:
460 | case T_OBJECT_OPERATOR:
461 | $n = $this->getNextNonWhitespaceTokenPos($tokens, $i);
462 | $nn = $this->getNextNonWhitespaceTokenPos($tokens, $n);
463 |
464 | if ($n && $nn &&
465 | isset($tokens[$n][0]) &&
466 | ($tokens[$n][0] === T_STRING ||
467 | $tokens[$n][0] === T_VARIABLE) &&
468 | $tokens[$nn] === '(') {
469 | if ($token === T_DOUBLE_COLON) {
470 | $this->collector->incrementStaticMethodCalls();
471 | } else {
472 | $this->collector->incrementNonStaticMethodCalls();
473 | }
474 | } else {
475 | if ($token === T_DOUBLE_COLON &&
476 | $tokens[$n][0] === T_VARIABLE) {
477 | $this->collector->incrementStaticAttributeAccesses();
478 | } elseif ($token === T_OBJECT_OPERATOR) {
479 | $this->collector->incrementNonStaticAttributeAccesses();
480 | }
481 | }
482 |
483 | break;
484 |
485 | case T_GLOBAL:
486 | $this->collector->incrementGlobalVariableAccesses();
487 |
488 | break;
489 |
490 | case T_VARIABLE:
491 | if ($value === '$GLOBALS') {
492 | $this->collector->incrementGlobalVariableAccesses();
493 | } elseif (isset($this->superGlobals[$value])) {
494 | $this->collector->incrementSuperGlobalVariableAccesses();
495 | }
496 |
497 | break;
498 |
499 | case T_USE:
500 | case T_DECLARE:
501 | $isLogicalLine = false;
502 |
503 | break;
504 | }
505 | }
506 | }
507 |
508 | /**
509 | * @param int $i
510 | *
511 | * @return string
512 | */
513 | private function getNamespaceName(array $tokens, $i)
514 | {
515 | if (isset($tokens[$i + 2][1])) {
516 | $namespace = $tokens[$i + 2][1];
517 |
518 | for ($j = $i + 3; ; $j += 2) {
519 | if (isset($tokens[$j]) && $tokens[$j][0] === T_NS_SEPARATOR) {
520 | $namespace .= '\\' . $tokens[$j + 1][1];
521 | } else {
522 | break;
523 | }
524 | }
525 |
526 | return $namespace;
527 | }
528 |
529 | return false;
530 | }
531 |
532 | /**
533 | * @param string $namespace
534 | * @param int $i
535 | *
536 | * @return string
537 | */
538 | private function getClassName($namespace, array $tokens, $i)
539 | {
540 | $i += 2;
541 |
542 | if (!isset($tokens[$i][1])) {
543 | return 'invalid class name';
544 | }
545 |
546 | $className = $tokens[$i][1];
547 |
548 | $namespaced = $className === '\\';
549 |
550 | while (isset($tokens[$i + 1]) && is_array($tokens[$i + 1]) && $tokens[$i + 1][0] !== T_WHITESPACE) {
551 | $className .= $tokens[++$i][1];
552 | }
553 |
554 | if (!$namespaced && $namespace !== false) {
555 | $className = $namespace . '\\' . $className;
556 | }
557 |
558 | return strtolower($className);
559 | }
560 |
561 | /**
562 | * @param string $className
563 | *
564 | * @return bool
565 | */
566 | private function isTestClass($className)
567 | {
568 | $parent = $this->classes[$className];
569 | $count = 0;
570 |
571 | // Check ancestry for PHPUnit_Framework_TestCase.
572 | while ($parent !== null) {
573 | $count++;
574 |
575 | if ($count > 100) {
576 | // Prevent infinite loops and just bail
577 | break;
578 | }
579 |
580 | if ($parent === 'phpunit_framework_testcase' ||
581 | $parent === '\\phpunit_framework_testcase' ||
582 | // TODO: Recognize PHPUnit\Framework\TestCase when it is imported
583 | $parent === 'phpunit\\framework\\testcase' ||
584 | $parent === '\\phpunit\\framework\\testcase') {
585 | return true;
586 | }
587 |
588 | if (isset($this->classes[$parent]) && $parent !== $this->classes[$parent]) {
589 | $parent = $this->classes[$parent];
590 | } else {
591 | // Class has a parent that is declared in a file
592 | // that was not pre-processed.
593 | break;
594 | }
595 | }
596 |
597 | // Fallback: Treat the class as a test case class if the name
598 | // of the parent class ends with "TestCase".
599 | return substr((string) $this->classes[$className], -8) === 'testcase';
600 | }
601 |
602 | /**
603 | * @param string $functionName
604 | * @param int $visibility
605 | * @param bool $static
606 | * @param int $currentToken
607 | *
608 | * @return bool
609 | */
610 | private function isTestMethod($functionName, $visibility, $static, array $tokens, $currentToken)
611 | {
612 | if ($static || $visibility != T_PUBLIC) {
613 | return false;
614 | }
615 |
616 | if (strpos($functionName, 'test') === 0) {
617 | return true;
618 | }
619 |
620 | while ($tokens[$currentToken][0] !== T_DOC_COMMENT) {
621 | if ($tokens[$currentToken] === '{' || $tokens[$currentToken] === '}') {
622 | return false;
623 | }
624 |
625 | $currentToken--;
626 | }
627 |
628 | return strpos($tokens[$currentToken][1], '@test') !== false ||
629 | strpos($tokens[$currentToken][1], '@scenario') !== false;
630 | }
631 |
632 | /**
633 | * @param int $start
634 | *
635 | * @return bool
636 | */
637 | private function getNextNonWhitespaceTokenPos(array $tokens, $start)
638 | {
639 | if (isset($tokens[$start + 1])) {
640 | if (isset($tokens[$start + 1][0]) &&
641 | $tokens[$start + 1][0] === T_WHITESPACE &&
642 | isset($tokens[$start + 2])) {
643 | return $start + 2;
644 | }
645 |
646 | return $start + 1;
647 | }
648 |
649 | return false;
650 | }
651 |
652 | /**
653 | * @param int $start
654 | *
655 | * @return bool
656 | */
657 | private function getPreviousNonWhitespaceTokenPos(array $tokens, $start)
658 | {
659 | if (isset($tokens[$start - 1])) {
660 | if (isset($tokens[$start - 1][0]) &&
661 | $tokens[$start - 1][0] === T_WHITESPACE &&
662 | isset($tokens[$start - 2])) {
663 | return $start - 2;
664 | }
665 |
666 | return $start - 1;
667 | }
668 |
669 | return false;
670 | }
671 |
672 | /**
673 | * @return bool
674 | */
675 | private function getPreviousNonWhitespaceNonCommentTokenPos(array $tokens, int $start)
676 | {
677 | $previousTokenIndex = $start - 1;
678 |
679 | if (isset($tokens[$previousTokenIndex])) {
680 | if (in_array($tokens[$previousTokenIndex][0], [
681 | T_WHITESPACE,
682 | T_COMMENT,
683 | T_DOC_COMMENT,
684 | ], true)
685 | ) {
686 | return $this->getPreviousNonWhitespaceNonCommentTokenPos($tokens, $previousTokenIndex);
687 | }
688 |
689 | return $previousTokenIndex;
690 | }
691 |
692 | return false;
693 | }
694 |
695 | /**
696 | * @param int $i
697 | *
698 | * @return bool
699 | */
700 | private function isClassDeclaration(array $tokens, $i)
701 | {
702 | $n = $this->getPreviousNonWhitespaceTokenPos($tokens, $i);
703 |
704 | return !isset($tokens[$n]) ||
705 | !is_array($tokens[$n]) ||
706 | !in_array($tokens[$n][0], [T_DOUBLE_COLON, T_NEW], true);
707 | }
708 | }
709 |
--------------------------------------------------------------------------------
/src/CLI/Application.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 | namespace Cmgmyr\PHPLOC;
11 |
12 | use const PHP_EOL;
13 | use function printf;
14 | use Cmgmyr\PHPLOC\Log\Csv as CsvPrinter;
15 | use Cmgmyr\PHPLOC\Log\Json as JsonPrinter;
16 | use Cmgmyr\PHPLOC\Log\Text as TextPrinter;
17 | use Cmgmyr\PHPLOC\Log\Xml as XmlPrinter;
18 | use SebastianBergmann\FileIterator\Facade;
19 |
20 | final class Application
21 | {
22 | private const VERSION = '8.0.6';
23 |
24 | public function run(array $argv): int
25 | {
26 | $this->printVersion();
27 |
28 | try {
29 | $arguments = (new ArgumentsBuilder)->build($argv);
30 | } catch (Exception $e) {
31 | print PHP_EOL . $e->getMessage() . PHP_EOL;
32 |
33 | return 1;
34 | }
35 |
36 | if ($arguments->version()) {
37 | return 0;
38 | }
39 |
40 | print PHP_EOL;
41 |
42 | if ($arguments->help()) {
43 | $this->help();
44 |
45 | return 0;
46 | }
47 |
48 | $files = [];
49 |
50 | foreach ($arguments->directories() as $directory) {
51 | $newFiles = (new Facade)->getFilesAsArray(
52 | $directory,
53 | $arguments->suffixes(),
54 | '',
55 | $arguments->exclude()
56 | );
57 | $files = $files + $newFiles;
58 | }
59 |
60 | if (empty($files)) {
61 | print 'No files found to scan' . PHP_EOL;
62 |
63 | return 1;
64 | }
65 |
66 | $result = (new Analyser)->countFiles($files, $arguments->countTests());
67 |
68 | (new TextPrinter)->printResult($result, $arguments->countTests());
69 |
70 | if ($arguments->csvLogfile()) {
71 | $printer = new CsvPrinter;
72 |
73 | $printer->printResult($arguments->csvLogfile(), $result);
74 | }
75 |
76 | if ($arguments->jsonLogfile()) {
77 | $printer = new JsonPrinter;
78 |
79 | $printer->printResult($arguments->jsonLogfile(), $result);
80 | }
81 |
82 | if ($arguments->xmlLogfile()) {
83 | $printer = new XmlPrinter;
84 |
85 | $printer->printResult($arguments->xmlLogfile(), $result);
86 | }
87 |
88 | return 0;
89 | }
90 |
91 | private function printVersion(): void
92 | {
93 | printf(
94 | 'phploc %s by Chris Gmyr.' . PHP_EOL,
95 | self::VERSION
96 | );
97 | }
98 |
99 | private function help(): void
100 | {
101 | print <<<'EOT'
102 | Usage:
103 | phploc [options]
104 |
105 | Options for selecting files:
106 |
107 | --suffix Include files with names ending in in the analysis
108 | (default: .php; can be given multiple times)
109 | --exclude Exclude files with in their path from the analysis
110 | (can be given multiple times)
111 |
112 | Options for analysing files:
113 |
114 | --count-tests Count PHPUnit test case classes and test methods
115 |
116 | Options for report generation:
117 |
118 | --log-csv Write results in CSV format to
119 | --log-json Write results in JSON format to
120 | --log-xml Write results in XML format to
121 |
122 | EOT;
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/CLI/Arguments.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 | namespace Cmgmyr\PHPLOC;
11 |
12 | final class Arguments
13 | {
14 | /**
15 | * @psalm-var list
16 | */
17 | private $directories;
18 |
19 | /**
20 | * @psalm-var list
21 | */
22 | private $suffixes;
23 |
24 | /**
25 | * @psalm-var list
26 | */
27 | private $exclude;
28 |
29 | /**
30 | * @var bool
31 | */
32 | private $countTests;
33 |
34 | /**
35 | * @var ?string
36 | */
37 | private $csvLogfile;
38 |
39 | /**
40 | * @var ?string
41 | */
42 | private $jsonLogfile;
43 |
44 | /**
45 | * @var ?string
46 | */
47 | private $xmlLogfile;
48 |
49 | /**
50 | * @var bool
51 | */
52 | private $help;
53 |
54 | /**
55 | * @var bool
56 | */
57 | private $version;
58 |
59 | public function __construct(array $directories, array $suffixes, array $exclude, bool $countTests, ?string $csvLogfile, ?string $jsonLogfile, ?string $xmlLogfile, bool $help, bool $version)
60 | {
61 | $this->directories = $directories;
62 | $this->suffixes = $suffixes;
63 | $this->exclude = $exclude;
64 | $this->countTests = $countTests;
65 | $this->csvLogfile = $csvLogfile;
66 | $this->jsonLogfile = $jsonLogfile;
67 | $this->xmlLogfile = $xmlLogfile;
68 | $this->help = $help;
69 | $this->version = $version;
70 | }
71 |
72 | /**
73 | * @psalm-return list
74 | */
75 | public function directories(): array
76 | {
77 | return $this->directories;
78 | }
79 |
80 | /**
81 | * @psalm-return list
82 | */
83 | public function suffixes(): array
84 | {
85 | return $this->suffixes;
86 | }
87 |
88 | /**
89 | * @psalm-return list
90 | */
91 | public function exclude(): array
92 | {
93 | return $this->exclude;
94 | }
95 |
96 | public function countTests(): bool
97 | {
98 | return $this->countTests;
99 | }
100 |
101 | public function csvLogfile(): ?string
102 | {
103 | return $this->csvLogfile;
104 | }
105 |
106 | public function jsonLogfile(): ?string
107 | {
108 | return $this->jsonLogfile;
109 | }
110 |
111 | public function xmlLogfile(): ?string
112 | {
113 | return $this->xmlLogfile;
114 | }
115 |
116 | public function help(): bool
117 | {
118 | return $this->help;
119 | }
120 |
121 | public function version(): bool
122 | {
123 | return $this->version;
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/src/CLI/ArgumentsBuilder.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 | namespace Cmgmyr\PHPLOC;
11 |
12 | use SebastianBergmann\CliParser\Exception as CliParserException;
13 | use SebastianBergmann\CliParser\Parser as CliParser;
14 |
15 | final class ArgumentsBuilder
16 | {
17 | /**
18 | * @throws ArgumentsBuilderException
19 | */
20 | public function build(array $argv): Arguments
21 | {
22 | try {
23 | $options = (new CliParser)->parse(
24 | $argv,
25 | 'hv',
26 | [
27 | 'suffix=',
28 | 'exclude=',
29 | 'count-tests',
30 | 'log-csv=',
31 | 'log-json=',
32 | 'log-xml=',
33 | 'help',
34 | 'version',
35 | ]
36 | );
37 | } catch (CliParserException $e) {
38 | throw new ArgumentsBuilderException(
39 | $e->getMessage(),
40 | (int) $e->getCode(),
41 | $e
42 | );
43 | }
44 |
45 | $directories = $options[1];
46 | $exclude = [];
47 | $suffixes = ['.php'];
48 | $countTests = false;
49 | $csvLogfile = null;
50 | $jsonLogfile = null;
51 | $xmlLogfile = null;
52 | $help = false;
53 | $version = false;
54 |
55 | foreach ($options[0] as $option) {
56 | switch ($option[0]) {
57 | case '--suffix':
58 | $suffixes[] = $option[1];
59 |
60 | break;
61 |
62 | case '--exclude':
63 | $exclude[] = $option[1];
64 |
65 | break;
66 |
67 | case '--count-tests':
68 | $countTests = true;
69 |
70 | break;
71 |
72 | case '--log-csv':
73 | $csvLogfile = $option[1];
74 |
75 | break;
76 |
77 | case '--log-json':
78 | $jsonLogfile = $option[1];
79 |
80 | break;
81 |
82 | case '--log-xml':
83 | $xmlLogfile = $option[1];
84 |
85 | break;
86 |
87 | case 'h':
88 | case '--help':
89 | $help = true;
90 |
91 | break;
92 |
93 | case 'v':
94 | case '--version':
95 | $version = true;
96 |
97 | break;
98 | }
99 | }
100 |
101 | if (empty($options[1]) && !$help && !$version) {
102 | throw new ArgumentsBuilderException(
103 | 'No directory specified'
104 | );
105 | }
106 |
107 | return new Arguments(
108 | $directories,
109 | $suffixes,
110 | $exclude,
111 | $countTests,
112 | $csvLogfile,
113 | $jsonLogfile,
114 | $xmlLogfile,
115 | $help,
116 | $version,
117 | );
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/Collector.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 | namespace Cmgmyr\PHPLOC;
11 |
12 | use function dirname;
13 |
14 | class Collector
15 | {
16 | private $counts = [];
17 |
18 | private $currentClassComplexity = 0;
19 |
20 | private $currentClassLines = 0;
21 |
22 | private $currentMethodComplexity = 0;
23 |
24 | private $currentMethodLines = 0;
25 |
26 | private $currentNumberOfMethods = 0;
27 |
28 | public function getPublisher()
29 | {
30 | return new Publisher($this->counts);
31 | }
32 |
33 | public function addFile($filename): void
34 | {
35 | $this->increment('files');
36 | $this->addUnique('directories', dirname($filename));
37 | }
38 |
39 | public function incrementLines($number): void
40 | {
41 | $this->increment('lines', $number);
42 | }
43 |
44 | public function incrementCommentLines($number): void
45 | {
46 | $this->increment('comment lines', $number);
47 | }
48 |
49 | public function incrementLogicalLines(): void
50 | {
51 | $this->increment('logical lines');
52 | }
53 |
54 | public function currentClassReset(): void
55 | {
56 | if ($this->currentClassComplexity > 0) {
57 | $this->addToArray('class complexity', $this->currentClassComplexity);
58 | $this->addToArray('class lines', $this->currentClassLines);
59 | }
60 | $this->currentClassComplexity = 0;
61 | $this->currentClassLines = 0;
62 | $this->currentNumberOfMethods = 0;
63 | }
64 |
65 | public function currentClassStop(): void
66 | {
67 | $this->addToArray('methods per class', $this->currentNumberOfMethods);
68 | }
69 |
70 | public function currentClassIncrementComplexity(): void
71 | {
72 | $this->currentClassComplexity++;
73 | }
74 |
75 | public function currentClassIncrementLines(): void
76 | {
77 | $this->currentClassLines++;
78 | }
79 |
80 | public function currentMethodStart(): void
81 | {
82 | $this->currentMethodComplexity = 1;
83 | $this->currentMethodLines = 0;
84 | }
85 |
86 | public function currentClassIncrementMethods(): void
87 | {
88 | $this->currentNumberOfMethods++;
89 | }
90 |
91 | public function currentMethodIncrementComplexity(): void
92 | {
93 | $this->currentMethodComplexity++;
94 | $this->increment('total method complexity');
95 | }
96 |
97 | public function currentMethodIncrementLines(): void
98 | {
99 | $this->currentMethodLines++;
100 | }
101 |
102 | public function currentMethodStop(): void
103 | {
104 | $this->addToArray('method complexity', $this->currentMethodComplexity);
105 | $this->addToArray('method lines', $this->currentMethodLines);
106 | }
107 |
108 | public function incrementFunctionLines(): void
109 | {
110 | $this->increment('function lines');
111 | }
112 |
113 | public function incrementComplexity(): void
114 | {
115 | $this->increment('complexity');
116 | }
117 |
118 | public function addPossibleConstantAccesses($name): void
119 | {
120 | $this->addToArray('possible constant accesses', $name);
121 | }
122 |
123 | public function addConstant($name): void
124 | {
125 | $this->addToArray('constant', $name);
126 | }
127 |
128 | public function incrementGlobalVariableAccesses(): void
129 | {
130 | $this->increment('global variable accesses');
131 | }
132 |
133 | public function incrementSuperGlobalVariableAccesses(): void
134 | {
135 | $this->increment('super global variable accesses');
136 | }
137 |
138 | public function incrementNonStaticAttributeAccesses(): void
139 | {
140 | $this->increment('non-static attribute accesses');
141 | }
142 |
143 | public function incrementStaticAttributeAccesses(): void
144 | {
145 | $this->increment('static attribute accesses');
146 | }
147 |
148 | public function incrementNonStaticMethodCalls(): void
149 | {
150 | $this->increment('non-static method calls');
151 | }
152 |
153 | public function incrementStaticMethodCalls(): void
154 | {
155 | $this->increment('static method calls');
156 | }
157 |
158 | public function addNamespace($namespace): void
159 | {
160 | $this->addUnique('namespaces', $namespace);
161 | }
162 |
163 | public function incrementInterfaces(): void
164 | {
165 | $this->increment('interfaces');
166 | }
167 |
168 | public function incrementTraits(): void
169 | {
170 | $this->increment('traits');
171 | }
172 |
173 | public function incrementAbstractClasses(): void
174 | {
175 | $this->increment('abstract classes');
176 | }
177 |
178 | public function incrementNonFinalClasses(): void
179 | {
180 | $this->increment('non-final classes');
181 | }
182 |
183 | public function incrementFinalClasses(): void
184 | {
185 | $this->increment('final classes');
186 | }
187 |
188 | public function incrementNonStaticMethods(): void
189 | {
190 | $this->increment('non-static methods');
191 | }
192 |
193 | public function incrementStaticMethods(): void
194 | {
195 | $this->increment('static methods');
196 | }
197 |
198 | public function incrementPublicMethods(): void
199 | {
200 | $this->increment('public methods');
201 | }
202 |
203 | public function incrementProtectedMethods(): void
204 | {
205 | $this->increment('protected methods');
206 | }
207 |
208 | public function incrementPrivateMethods(): void
209 | {
210 | $this->increment('private methods');
211 | }
212 |
213 | public function incrementNamedFunctions(): void
214 | {
215 | $this->increment('named functions');
216 | }
217 |
218 | public function incrementAnonymousFunctions(): void
219 | {
220 | $this->increment('anonymous functions');
221 | }
222 |
223 | public function incrementGlobalConstants(): void
224 | {
225 | $this->increment('global constants');
226 | }
227 |
228 | public function incrementPublicClassConstants(): void
229 | {
230 | $this->increment('public class constants');
231 | }
232 |
233 | public function incrementNonPublicClassConstants(): void
234 | {
235 | $this->increment('non-public class constants');
236 | }
237 |
238 | public function incrementTestClasses(): void
239 | {
240 | $this->increment('test classes');
241 | }
242 |
243 | public function incrementTestMethods(): void
244 | {
245 | $this->increment('test methods');
246 | }
247 |
248 | private function addUnique($key, $name): void
249 | {
250 | $this->check($key, []);
251 | $this->counts[$key][$name] = true;
252 | }
253 |
254 | private function addToArray($key, $value): void
255 | {
256 | $this->check($key, []);
257 | $this->counts[$key][] = $value;
258 | }
259 |
260 | private function increment($key, $number = 1): void
261 | {
262 | $this->check($key, 0);
263 | $this->counts[$key] += $number;
264 | }
265 |
266 | private function check($key, $default): void
267 | {
268 | if (!isset($this->counts[$key])) {
269 | $this->counts[$key] = $default;
270 | }
271 | }
272 | }
273 |
--------------------------------------------------------------------------------
/src/Exception/ArgumentsBuilderException.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 | namespace Cmgmyr\PHPLOC;
11 |
12 | use RuntimeException;
13 |
14 | final class ArgumentsBuilderException extends RuntimeException implements Exception
15 | {
16 | }
17 |
--------------------------------------------------------------------------------
/src/Exception/Exception.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 | namespace Cmgmyr\PHPLOC;
11 |
12 | use Throwable;
13 |
14 | interface Exception extends Throwable
15 | {
16 | }
17 |
--------------------------------------------------------------------------------
/src/Exception/RuntimeException.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 | namespace Cmgmyr\PHPLOC;
11 |
12 | class RuntimeException extends \RuntimeException implements Exception
13 | {
14 | }
15 |
--------------------------------------------------------------------------------
/src/Log/Csv.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 | namespace Cmgmyr\PHPLOC\Log;
11 |
12 | use const PHP_EOL;
13 | use function array_values;
14 | use function file_put_contents;
15 | use function implode;
16 | use InvalidArgumentException;
17 |
18 | final class Csv
19 | {
20 | private $colmap = [
21 | 'directories' => 'Directories',
22 | 'files' => 'Files',
23 | 'loc' => 'Lines of Code (LOC)',
24 | 'ccnByLloc' => 'Cyclomatic Complexity / Lines of Code',
25 | 'cloc' => 'Comment Lines of Code (CLOC)',
26 | 'ncloc' => 'Non-Comment Lines of Code (NCLOC)',
27 | 'lloc' => 'Logical Lines of Code (LLOC)',
28 | 'llocGlobal' => 'LLOC outside functions or classes',
29 | 'namespaces' => 'Namespaces',
30 | 'interfaces' => 'Interfaces',
31 | 'traits' => 'Traits',
32 | 'classes' => 'Classes',
33 | 'abstractClasses' => 'Abstract Classes',
34 | 'concreteClasses' => 'Concrete Classes',
35 | 'finalClasses' => 'Final Classes',
36 | 'nonFinalClasses' => 'Non-Final Classes',
37 | 'llocClasses' => 'Classes Length (LLOC)',
38 | 'methods' => 'Methods',
39 | 'nonStaticMethods' => 'Non-Static Methods',
40 | 'staticMethods' => 'Static Methods',
41 | 'publicMethods' => 'Public Methods',
42 | 'nonPublicMethods' => 'Non-Public Methods',
43 | 'protectedMethods' => 'Protected Methods',
44 | 'privateMethods' => 'Private Methods',
45 | 'classCcnAvg' => 'Cyclomatic Complexity / Number of Classes' /* In Text output: 'Average Complexity per Class' */,
46 | 'methodCcnAvg' => 'Cyclomatic Complexity / Number of Methods',
47 | 'functions' => 'Functions',
48 | 'namedFunctions' => 'Named Functions',
49 | 'anonymousFunctions' => 'Anonymous Functions',
50 | 'llocFunctions' => 'Functions Length (LLOC)',
51 | 'llocByNof' => 'Average Function Length (LLOC)',
52 | 'classLlocAvg' => 'Average Class Length',
53 | 'methodLlocAvg' => 'Average Method Length',
54 | 'averageMethodsPerClass' => 'Average Methods per Class',
55 | 'constants' => 'Constants',
56 | 'globalConstants' => 'Global Constants',
57 | 'classConstants' => 'Class Constants',
58 | 'publicClassConstants' => 'Public Class Constants',
59 | 'nonPublicClassConstants' => 'Non-Public Class Constants',
60 | 'attributeAccesses' => 'Attribute Accesses',
61 | 'instanceAttributeAccesses' => 'Non-Static Attribute Accesses',
62 | 'staticAttributeAccesses' => 'Static Attribute Accesses',
63 | 'methodCalls' => 'Method Calls',
64 | 'instanceMethodCalls' => 'Non-Static Method Calls',
65 | 'staticMethodCalls' => 'Static Method Calls',
66 | 'globalAccesses' => 'Global Accesses',
67 | 'globalVariableAccesses' => 'Global Variable Accesses',
68 | 'superGlobalVariableAccesses' => 'Super-Global Variable Accesses',
69 | 'globalConstantAccesses' => 'Global Constant Accesses',
70 | 'testClasses' => 'Test Classes',
71 | 'testMethods' => 'Test Methods',
72 | ];
73 |
74 | public function printResult(string $filename, array $count): void
75 | {
76 | file_put_contents(
77 | $filename,
78 | $this->getKeysLine($count) . $this->getValuesLine($count)
79 | );
80 | }
81 |
82 | private function getKeysLine(array $count): string
83 | {
84 | return implode(',', array_values($this->colmap)) . PHP_EOL;
85 | }
86 |
87 | /**
88 | * @throws InvalidArgumentException
89 | */
90 | private function getValuesLine(array $count): string
91 | {
92 | $values = [];
93 |
94 | foreach ($this->colmap as $key => $name) {
95 | if (isset($count[$key])) {
96 | $values[] = $count[$key];
97 | } else {
98 | throw new InvalidArgumentException('Attempted to print row with missing keys');
99 | }
100 | }
101 |
102 | return '"' . implode('","', $values) . '"' . PHP_EOL;
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/Log/Json.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 | namespace Cmgmyr\PHPLOC\Log;
11 |
12 | use const JSON_PRETTY_PRINT;
13 | use function array_merge;
14 | use function file_put_contents;
15 | use function json_encode;
16 |
17 | final class Json
18 | {
19 | public function printResult(string $filename, array $count): void
20 | {
21 | $directories = [];
22 |
23 | if ($count['directories'] > 0) {
24 | $directories = [
25 | 'directories' => $count['directories'],
26 | 'files' => $count['files'],
27 | ];
28 | }
29 |
30 | unset($count['directories'], $count['files']);
31 |
32 | $report = array_merge($directories, $count);
33 |
34 | file_put_contents(
35 | $filename,
36 | json_encode($report, JSON_PRETTY_PRINT)
37 | );
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Log/Text.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 | namespace Cmgmyr\PHPLOC\Log;
11 |
12 | use const PHP_EOL;
13 |
14 | final class Text
15 | {
16 | public function printResult(array $count, bool $printTests): void
17 | {
18 | if ($count['directories'] > 0) {
19 | \printf(
20 | 'Directories %10d' . PHP_EOL .
21 | 'Files %10d' . PHP_EOL . PHP_EOL,
22 | $count['directories'],
23 | $count['files']
24 | );
25 | }
26 |
27 | $format = <<<'END'
28 | Size
29 | Lines of Code (LOC) %10d
30 | Comment Lines of Code (CLOC) %10d (%.2f%%)
31 | Non-Comment Lines of Code (NCLOC) %10d (%.2f%%)
32 | Logical Lines of Code (LLOC) %10d (%.2f%%)
33 | Classes %10d (%.2f%%)
34 | Average Class Length %10d
35 | Minimum Class Length %10d
36 | Maximum Class Length %10d
37 | Average Method Length %10d
38 | Minimum Method Length %10d
39 | Maximum Method Length %10d
40 | Average Methods Per Class %10d
41 | Minimum Methods Per Class %10d
42 | Maximum Methods Per Class %10d
43 | Functions %10d (%.2f%%)
44 | Average Function Length %10d
45 | Not in classes or functions %10d (%.2f%%)
46 |
47 | Cyclomatic Complexity
48 | Average Complexity per LLOC %10.2f
49 | Average Complexity per Class %10.2f
50 | Minimum Class Complexity %10.2f
51 | Maximum Class Complexity %10.2f
52 | Average Complexity per Method %10.2f
53 | Minimum Method Complexity %10.2f
54 | Maximum Method Complexity %10.2f
55 |
56 | Dependencies
57 | Global Accesses %10d
58 | Global Constants %10d (%.2f%%)
59 | Global Variables %10d (%.2f%%)
60 | Super-Global Variables %10d (%.2f%%)
61 | Attribute Accesses %10d
62 | Non-Static %10d (%.2f%%)
63 | Static %10d (%.2f%%)
64 | Method Calls %10d
65 | Non-Static %10d (%.2f%%)
66 | Static %10d (%.2f%%)
67 |
68 | Structure
69 | Namespaces %10d
70 | Interfaces %10d
71 | Traits %10d
72 | Classes %10d
73 | Abstract Classes %10d (%.2f%%)
74 | Concrete Classes %10d (%.2f%%)
75 | Final Classes %10d (%.2f%%)
76 | Non-Final Classes %10d (%.2f%%)
77 | Methods %10d
78 | Scope
79 | Non-Static Methods %10d (%.2f%%)
80 | Static Methods %10d (%.2f%%)
81 | Visibility
82 | Public Methods %10d (%.2f%%)
83 | Protected Methods %10d (%.2f%%)
84 | Private Methods %10d (%.2f%%)
85 | Functions %10d
86 | Named Functions %10d (%.2f%%)
87 | Anonymous Functions %10d (%.2f%%)
88 | Constants %10d
89 | Global Constants %10d (%.2f%%)
90 | Class Constants %10d (%.2f%%)
91 | Public Constants %10d (%.2f%%)
92 | Non-Public Constants %10d (%.2f%%)
93 |
94 | END;
95 |
96 | printf(
97 | $format,
98 | $count['loc'],
99 | $count['cloc'],
100 | $count['loc'] > 0 ? ($count['cloc'] / $count['loc']) * 100 : 0,
101 | $count['ncloc'],
102 | $count['loc'] > 0 ? ($count['ncloc'] / $count['loc']) * 100 : 0,
103 | $count['lloc'],
104 | $count['loc'] > 0 ? ($count['lloc'] / $count['loc']) * 100 : 0,
105 | $count['llocClasses'],
106 | $count['lloc'] > 0 ? ($count['llocClasses'] / $count['lloc']) * 100 : 0,
107 | $count['classLlocAvg'],
108 | $count['classLlocMin'],
109 | $count['classLlocMax'],
110 | $count['methodLlocAvg'],
111 | $count['methodLlocMin'],
112 | $count['methodLlocMax'],
113 | $count['averageMethodsPerClass'],
114 | $count['minimumMethodsPerClass'],
115 | $count['maximumMethodsPerClass'],
116 | $count['llocFunctions'],
117 | $count['lloc'] > 0 ? ($count['llocFunctions'] / $count['lloc']) * 100 : 0,
118 | $count['llocByNof'],
119 | $count['llocGlobal'],
120 | $count['lloc'] > 0 ? ($count['llocGlobal'] / $count['lloc']) * 100 : 0,
121 | $count['ccnByLloc'],
122 | $count['classCcnAvg'],
123 | $count['classCcnMin'],
124 | $count['classCcnMax'],
125 | $count['methodCcnAvg'],
126 | $count['methodCcnMin'],
127 | $count['methodCcnMax'],
128 | $count['globalAccesses'],
129 | $count['globalConstantAccesses'],
130 | $count['globalAccesses'] > 0 ? ($count['globalConstantAccesses'] / $count['globalAccesses']) * 100 : 0,
131 | $count['globalVariableAccesses'],
132 | $count['globalAccesses'] > 0 ? ($count['globalVariableAccesses'] / $count['globalAccesses']) * 100 : 0,
133 | $count['superGlobalVariableAccesses'],
134 | $count['globalAccesses'] > 0 ? ($count['superGlobalVariableAccesses'] / $count['globalAccesses']) * 100 : 0,
135 | $count['attributeAccesses'],
136 | $count['instanceAttributeAccesses'],
137 | $count['attributeAccesses'] > 0 ? ($count['instanceAttributeAccesses'] / $count['attributeAccesses']) * 100 : 0,
138 | $count['staticAttributeAccesses'],
139 | $count['attributeAccesses'] > 0 ? ($count['staticAttributeAccesses'] / $count['attributeAccesses']) * 100 : 0,
140 | $count['methodCalls'],
141 | $count['instanceMethodCalls'],
142 | $count['methodCalls'] > 0 ? ($count['instanceMethodCalls'] / $count['methodCalls']) * 100 : 0,
143 | $count['staticMethodCalls'],
144 | $count['methodCalls'] > 0 ? ($count['staticMethodCalls'] / $count['methodCalls']) * 100 : 0,
145 | $count['namespaces'],
146 | $count['interfaces'],
147 | $count['traits'],
148 | $count['classes'],
149 | $count['abstractClasses'],
150 | $count['classes'] > 0 ? ($count['abstractClasses'] / $count['classes']) * 100 : 0,
151 | $count['concreteClasses'],
152 | $count['classes'] > 0 ? ($count['concreteClasses'] / $count['classes']) * 100 : 0,
153 | $count['finalClasses'],
154 | $count['concreteClasses'] > 0 ? ($count['finalClasses'] / $count['concreteClasses']) * 100 : 0,
155 | $count['nonFinalClasses'],
156 | $count['concreteClasses'] > 0 ? ($count['nonFinalClasses'] / $count['concreteClasses']) * 100 : 0,
157 | $count['methods'],
158 | $count['nonStaticMethods'],
159 | $count['methods'] > 0 ? ($count['nonStaticMethods'] / $count['methods']) * 100 : 0,
160 | $count['staticMethods'],
161 | $count['methods'] > 0 ? ($count['staticMethods'] / $count['methods']) * 100 : 0,
162 | $count['publicMethods'],
163 | $count['methods'] > 0 ? ($count['publicMethods'] / $count['methods']) * 100 : 0,
164 | $count['protectedMethods'],
165 | $count['methods'] > 0 ? ($count['protectedMethods'] / $count['methods']) * 100 : 0,
166 | $count['privateMethods'],
167 | $count['methods'] > 0 ? ($count['privateMethods'] / $count['methods']) * 100 : 0,
168 | $count['functions'],
169 | $count['namedFunctions'],
170 | $count['functions'] > 0 ? ($count['namedFunctions'] / $count['functions']) * 100 : 0,
171 | $count['anonymousFunctions'],
172 | $count['functions'] > 0 ? ($count['anonymousFunctions'] / $count['functions']) * 100 : 0,
173 | $count['constants'],
174 | $count['globalConstants'],
175 | $count['constants'] > 0 ? ($count['globalConstants'] / $count['constants']) * 100 : 0,
176 | $count['classConstants'],
177 | $count['constants'] > 0 ? ($count['classConstants'] / $count['constants']) * 100 : 0,
178 | $count['publicClassConstants'],
179 | $count['classConstants'] > 0 ? ($count['publicClassConstants'] / $count['classConstants']) * 100 : 0,
180 | $count['nonPublicClassConstants'],
181 | $count['classConstants'] > 0 ? ($count['nonPublicClassConstants'] / $count['classConstants']) * 100 : 0
182 | );
183 |
184 | if ($printTests) {
185 | \printf(
186 | PHP_EOL . 'Tests' . PHP_EOL .
187 | ' Classes %10d' . PHP_EOL .
188 | ' Methods %10d' . PHP_EOL,
189 | $count['testClasses'],
190 | $count['testMethods']
191 | );
192 | }
193 | }
194 | }
195 |
--------------------------------------------------------------------------------
/src/Log/Xml.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 | namespace Cmgmyr\PHPLOC\Log;
11 |
12 | use function file_put_contents;
13 | use DOMDocument;
14 |
15 | final class Xml
16 | {
17 | /** @noinspection UnusedFunctionResultInspection */
18 | public function printResult(string $filename, array $count): void
19 | {
20 | $document = new DOMDocument('1.0', 'UTF-8');
21 | $document->formatOutput = true;
22 |
23 | $root = $document->createElement('phploc');
24 |
25 | $document->appendChild($root);
26 |
27 | if ($count['directories'] > 0) {
28 | $root->appendChild(
29 | $document->createElement('directories', (string) $count['directories'])
30 | );
31 |
32 | $root->appendChild(
33 | $document->createElement('files', (string) $count['files'])
34 | );
35 | }
36 |
37 | unset($count['directories'], $count['files']);
38 |
39 | foreach ($count as $k => $v) {
40 | $root->appendChild(
41 | $document->createElement($k, (string) $v)
42 | );
43 | }
44 |
45 | file_put_contents($filename, $document->saveXML());
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Publisher.php:
--------------------------------------------------------------------------------
1 |
6 | *
7 | * For the full copyright and license information, please view the LICENSE
8 | * file that was distributed with this source code.
9 | */
10 | namespace Cmgmyr\PHPLOC;
11 |
12 | use function array_intersect;
13 | use function array_sum;
14 | use function count;
15 | use function max;
16 | use function min;
17 |
18 | class Publisher
19 | {
20 | private $counts;
21 |
22 | public function __construct(array $counts)
23 | {
24 | $this->counts = $counts;
25 | }
26 |
27 | public function getDirectories()
28 | {
29 | return $this->getCount('directories') - 1;
30 | }
31 |
32 | public function getFiles()
33 | {
34 | return $this->getValue('files');
35 | }
36 |
37 | public function getLines()
38 | {
39 | return $this->getValue('lines');
40 | }
41 |
42 | public function getCommentLines()
43 | {
44 | return $this->getValue('comment lines');
45 | }
46 |
47 | public function getNonCommentLines()
48 | {
49 | return $this->getLines() - $this->getCommentLines();
50 | }
51 |
52 | public function getLogicalLines()
53 | {
54 | return $this->getValue('logical lines');
55 | }
56 |
57 | public function getClassLines()
58 | {
59 | return $this->getSum('class lines');
60 | }
61 |
62 | public function getAverageClassLength()
63 | {
64 | return $this->getAverage('class lines');
65 | }
66 |
67 | public function getMinimumClassLength()
68 | {
69 | return $this->getMinimum('class lines');
70 | }
71 |
72 | public function getMaximumClassLength()
73 | {
74 | return $this->getMaximum('class lines');
75 | }
76 |
77 | public function getAverageMethodLength()
78 | {
79 | return $this->getAverage('method lines');
80 | }
81 |
82 | public function getMinimumMethodLength()
83 | {
84 | return $this->getMinimum('method lines');
85 | }
86 |
87 | public function getMaximumMethodLength()
88 | {
89 | return $this->getMaximum('method lines');
90 | }
91 |
92 | public function getAverageMethodsPerClass()
93 | {
94 | return $this->getAverage('methods per class');
95 | }
96 |
97 | public function getMinimumMethodsPerClass()
98 | {
99 | return $this->getMinimum('methods per class');
100 | }
101 |
102 | public function getMaximumMethodsPerClass()
103 | {
104 | return $this->getMaximum('methods per class');
105 | }
106 |
107 | public function getFunctionLines()
108 | {
109 | return $this->getValue('function lines');
110 | }
111 |
112 | public function getAverageFunctionLength()
113 | {
114 | return $this->divide($this->getFunctionLines(), $this->getFunctions());
115 | }
116 |
117 | public function getNotInClassesOrFunctions()
118 | {
119 | return $this->getLogicalLines() - $this->getClassLines() - $this->getFunctionLines();
120 | }
121 |
122 | public function getComplexity()
123 | {
124 | return $this->getValue('complexity');
125 | }
126 |
127 | public function getMethodComplexity()
128 | {
129 | return $this->getValue('total method complexity');
130 | }
131 |
132 | public function getAverageComplexityPerLogicalLine()
133 | {
134 | return $this->divide($this->getComplexity(), $this->getLogicalLines());
135 | }
136 |
137 | public function getAverageComplexityPerClass()
138 | {
139 | return $this->getAverage('class complexity');
140 | }
141 |
142 | public function getMinimumClassComplexity()
143 | {
144 | return $this->getMinimum('class complexity');
145 | }
146 |
147 | public function getMaximumClassComplexity()
148 | {
149 | return $this->getMaximum('class complexity');
150 | }
151 |
152 | public function getAverageComplexityPerMethod()
153 | {
154 | return $this->getAverage('method complexity');
155 | }
156 |
157 | public function getMinimumMethodComplexity()
158 | {
159 | return $this->getMinimum('method complexity');
160 | }
161 |
162 | public function getMaximumMethodComplexity()
163 | {
164 | return $this->getMaximum('method complexity');
165 | }
166 |
167 | public function getGlobalAccesses()
168 | {
169 | return $this->getGlobalConstantAccesses() + $this->getGlobalVariableAccesses() + $this->getSuperGlobalVariableAccesses();
170 | }
171 |
172 | public function getGlobalConstantAccesses()
173 | {
174 | return count(array_intersect($this->getValue('possible constant accesses', []), $this->getValue('constant', [])));
175 | }
176 |
177 | public function getGlobalVariableAccesses()
178 | {
179 | return $this->getValue('global variable accesses');
180 | }
181 |
182 | public function getSuperGlobalVariableAccesses()
183 | {
184 | return $this->getValue('super global variable accesses');
185 | }
186 |
187 | public function getAttributeAccesses()
188 | {
189 | return $this->getNonStaticAttributeAccesses() + $this->getStaticAttributeAccesses();
190 | }
191 |
192 | public function getNonStaticAttributeAccesses()
193 | {
194 | return $this->getValue('non-static attribute accesses');
195 | }
196 |
197 | public function getStaticAttributeAccesses()
198 | {
199 | return $this->getValue('static attribute accesses');
200 | }
201 |
202 | public function getMethodCalls()
203 | {
204 | return $this->getNonStaticMethodCalls() + $this->getStaticMethodCalls();
205 | }
206 |
207 | public function getNonStaticMethodCalls()
208 | {
209 | return $this->getValue('non-static method calls');
210 | }
211 |
212 | public function getStaticMethodCalls()
213 | {
214 | return $this->getValue('static method calls');
215 | }
216 |
217 | public function getNamespaces()
218 | {
219 | return $this->getCount('namespaces');
220 | }
221 |
222 | public function getInterfaces()
223 | {
224 | return $this->getValue('interfaces');
225 | }
226 |
227 | public function getTraits()
228 | {
229 | return $this->getValue('traits');
230 | }
231 |
232 | public function getClasses()
233 | {
234 | return $this->getAbstractClasses() + $this->getConcreteClasses();
235 | }
236 |
237 | public function getAbstractClasses()
238 | {
239 | return $this->getValue('abstract classes');
240 | }
241 |
242 | public function getConcreteClasses()
243 | {
244 | return $this->getFinalClasses() + $this->getNonFinalClasses();
245 | }
246 |
247 | public function getFinalClasses()
248 | {
249 | return $this->getValue('final classes');
250 | }
251 |
252 | public function getNonFinalClasses()
253 | {
254 | return $this->getValue('non-final classes');
255 | }
256 |
257 | public function getMethods()
258 | {
259 | return $this->getNonStaticMethods() + $this->getStaticMethods();
260 | }
261 |
262 | public function getNonStaticMethods()
263 | {
264 | return $this->getValue('non-static methods');
265 | }
266 |
267 | public function getStaticMethods()
268 | {
269 | return $this->getValue('static methods');
270 | }
271 |
272 | public function getPublicMethods()
273 | {
274 | return $this->getValue('public methods');
275 | }
276 |
277 | public function getNonPublicMethods()
278 | {
279 | return $this->getProtectedMethods() + $this->getPrivateMethods();
280 | }
281 |
282 | public function getProtectedMethods()
283 | {
284 | return $this->getValue('protected methods');
285 | }
286 |
287 | public function getPrivateMethods()
288 | {
289 | return $this->getValue('private methods');
290 | }
291 |
292 | public function getFunctions()
293 | {
294 | return $this->getNamedFunctions() + $this->getAnonymousFunctions();
295 | }
296 |
297 | public function getNamedFunctions()
298 | {
299 | return $this->getValue('named functions');
300 | }
301 |
302 | public function getAnonymousFunctions()
303 | {
304 | return $this->getValue('anonymous functions');
305 | }
306 |
307 | public function getConstants()
308 | {
309 | return $this->getGlobalConstants() + $this->getClassConstants();
310 | }
311 |
312 | public function getGlobalConstants()
313 | {
314 | return $this->getValue('global constants');
315 | }
316 |
317 | public function getPublicClassConstants()
318 | {
319 | return $this->getValue('public class constants');
320 | }
321 |
322 | public function getNonPublicClassConstants()
323 | {
324 | return $this->getValue('non-public class constants');
325 | }
326 |
327 | public function getClassConstants()
328 | {
329 | return $this->getPublicClassConstants() + $this->getNonPublicClassConstants();
330 | }
331 |
332 | public function getTestClasses()
333 | {
334 | return $this->getValue('test classes');
335 | }
336 |
337 | public function getTestMethods()
338 | {
339 | return $this->getValue('test methods');
340 | }
341 |
342 | public function toArray()
343 | {
344 | return [
345 | 'files' => $this->getFiles(),
346 | 'loc' => $this->getLines(),
347 | 'lloc' => $this->getLogicalLines(),
348 | 'llocClasses' => $this->getClassLines(),
349 | 'llocFunctions' => $this->getFunctionLines(),
350 | 'llocGlobal' => $this->getNotInClassesOrFunctions(),
351 | 'cloc' => $this->getCommentLines(),
352 | 'ccn' => $this->getComplexity(),
353 | 'ccnMethods' => $this->getMethodComplexity(),
354 | 'interfaces' => $this->getInterfaces(),
355 | 'traits' => $this->getTraits(),
356 | 'classes' => $this->getClasses(),
357 | 'abstractClasses' => $this->getAbstractClasses(),
358 | 'concreteClasses' => $this->getConcreteClasses(),
359 | 'finalClasses' => $this->getFinalClasses(),
360 | 'nonFinalClasses' => $this->getNonFinalClasses(),
361 | 'functions' => $this->getFunctions(),
362 | 'namedFunctions' => $this->getNamedFunctions(),
363 | 'anonymousFunctions' => $this->getAnonymousFunctions(),
364 | 'methods' => $this->getMethods(),
365 | 'publicMethods' => $this->getPublicMethods(),
366 | 'nonPublicMethods' => $this->getNonPublicMethods(),
367 | 'protectedMethods' => $this->getProtectedMethods(),
368 | 'privateMethods' => $this->getPrivateMethods(),
369 | 'nonStaticMethods' => $this->getNonStaticMethods(),
370 | 'staticMethods' => $this->getStaticMethods(),
371 | 'constants' => $this->getConstants(),
372 | 'classConstants' => $this->getClassConstants(),
373 | 'publicClassConstants' => $this->getPublicClassConstants(),
374 | 'nonPublicClassConstants' => $this->getNonPublicClassConstants(),
375 | 'globalConstants' => $this->getGlobalConstants(),
376 | 'testClasses' => $this->getTestClasses(),
377 | 'testMethods' => $this->getTestMethods(),
378 | 'ccnByLloc' => $this->getAverageComplexityPerLogicalLine(),
379 | 'llocByNof' => $this->getAverageFunctionLength(),
380 | 'methodCalls' => $this->getMethodCalls(),
381 | 'staticMethodCalls' => $this->getStaticMethodCalls(),
382 | 'instanceMethodCalls' => $this->getNonStaticMethodCalls(),
383 | 'attributeAccesses' => $this->getAttributeAccesses(),
384 | 'staticAttributeAccesses' => $this->getStaticAttributeAccesses(),
385 | 'instanceAttributeAccesses' => $this->getNonStaticAttributeAccesses(),
386 | 'globalAccesses' => $this->getGlobalAccesses(),
387 | 'globalVariableAccesses' => $this->getGlobalVariableAccesses(),
388 | 'superGlobalVariableAccesses' => $this->getSuperGlobalVariableAccesses(),
389 | 'globalConstantAccesses' => $this->getGlobalConstantAccesses(),
390 | 'directories' => $this->getDirectories(),
391 | 'classCcnMin' => $this->getMinimumClassComplexity(),
392 | 'classCcnAvg' => $this->getAverageComplexityPerClass(),
393 | 'classCcnMax' => $this->getMaximumClassComplexity(),
394 | 'classLlocMin' => $this->getMinimumClassLength(),
395 | 'classLlocAvg' => $this->getAverageClassLength(),
396 | 'classLlocMax' => $this->getMaximumClassLength(),
397 | 'methodCcnMin' => $this->getMinimumMethodComplexity(),
398 | 'methodCcnAvg' => $this->getAverageComplexityPerMethod(),
399 | 'methodCcnMax' => $this->getMaximumMethodComplexity(),
400 | 'methodLlocMin' => $this->getMinimumMethodLength(),
401 | 'methodLlocAvg' => $this->getAverageMethodLength(),
402 | 'methodLlocMax' => $this->getMaximumMethodLength(),
403 | 'averageMethodsPerClass' => $this->getAverageMethodsPerClass(),
404 | 'minimumMethodsPerClass' => $this->getMinimumMethodsPerClass(),
405 | 'maximumMethodsPerClass' => $this->getMaximumMethodsPerClass(),
406 | 'namespaces' => $this->getNamespaces(),
407 | 'ncloc' => $this->getNonCommentLines(),
408 | ];
409 | }
410 |
411 | private function getAverage($key)
412 | {
413 | return $this->divide($this->getSum($key), $this->getCount($key));
414 | }
415 |
416 | private function getCount($key)
417 | {
418 | return isset($this->counts[$key]) ? count($this->counts[$key]) : 0;
419 | }
420 |
421 | private function getSum($key)
422 | {
423 | return isset($this->counts[$key]) ? array_sum($this->counts[$key]) : 0;
424 | }
425 |
426 | private function getMaximum($key)
427 | {
428 | return isset($this->counts[$key]) ? max($this->counts[$key]) : 0;
429 | }
430 |
431 | private function getMinimum($key)
432 | {
433 | return isset($this->counts[$key]) ? min($this->counts[$key]) : 0;
434 | }
435 |
436 | private function getValue($key, $default = 0)
437 | {
438 | return $this->counts[$key] ?? $default;
439 | }
440 |
441 | private function divide($x, $y)
442 | {
443 | return $y != 0 ? $x / $y : 0;
444 | }
445 | }
446 |
--------------------------------------------------------------------------------