├── .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 | --------------------------------------------------------------------------------