Phan

From Freephile Wiki

Phan is a static analyzer for PHP. It apparently originated from Etsy. It will help you write better PHP code. Please note that your source code should follow best practices for directory layout[1]. To use it, you'll need to install the Abstract Syntax Tree PHP extension. See the tutorial for analyzing a large sloppy codebase

Phan project on GitHub

The MediaWiki project uses Phan in its Continuous Integration.

Links[edit | edit source]

  1. mediawiki-tools-phan on github
  2. phan/phan on github
  3. nikic/php-ast on github
  4. Phan Getting-Started
  5. Phan CLI-HELP.md

Static Analysis of MediaWiki[edit | edit source]

For some current analysis see https://doc.wikimedia.org/mediawiki-core/master/phpmetrics/complexity.html

  1. Continuous_integration/Entry_points
  2. Continuous_integration/Phan
  3. Continuous_integration/Tutorials/Add_phan_to_a_MediaWiki_extension
  4. Best_practices_for_extensions#File_structure

The on-wiki documentation (at MediaWiki.org) and even the upstream projects do not exactly provide a usable guide for MediaWiki implementors to use Phan on an extensive codebase (ie. "my whole wiki"). Instead, the documentation describes how MediaWiki extension developers can use Phan by incorporating their code into the larger configuration of MediaWiki Continuous Integration. If you download MediaWiki and composer update (to get dev dependencies) and also install PHP-AST, then you should be able to run composer phan or ./vendor/bin/phan -p . But, that analyzes the entire MediaWiki codebase (using a lot of RAM and time), and does not analyze random extensions (found on client's wiki instance) unless those extensions already have their own ./phan/config.php

The traditional form of that[2], for an extension named "MyExtension", which has dependencies on the Echo and SocialProfile extension, is

$cfg = require __DIR__ . '/../vendor/mediawiki/mediawiki-phan-config/src/config.php';

$cfg['directory_list'] = array_merge(
	$cfg['directory_list'],
	[
		'../../extensions/Echo',
		'../../extensions/SocialProfile',
	]
);
$cfg['exclude_analysis_directory_list'] = array_merge(
	$cfg['exclude_analysis_directory_list'],
	[
		'../../extensions/Echo',
		'../../extensions/SocialProfile',
	]
);

return $cfg;

The reason the normal pattern is to include directories and exclude those same directories is that you include 'dependencies' for class definitions etc. (symbol discovery), but do not warn about issues outside the limits of your own MyExtension extension.

MediaWiki Phan Config[edit | edit source]

The bigger (unanswered) question is what does mediawiki-phan-config do? What is the compiled and complete configuration that you are "running" against your codebase?

For example, one basic attribute is the "severity level" or rigor of the analysis. What is that set to for MediaWiki code? Phan has a CLI option named

-y, --minimum-severity <level>

The level can be any digit from 0-10, defaulting to 0. Zero means report every possible problem. Minimum severity of 10 would report only 'critical' problems.

MediaWiki Phan Config has a ConfigBuilder.php class that is used to build up the project's phan config. The main file to see how it's configured is config.php

After reading that and the source of ./phan/config.php, we figured out the full configuration, and that you can create your own overrides/customizations in a file called .phan/local-config.php

Fully parsed configuration[edit | edit source]

Show the full configuration for MediaWiki phan

In .phan/config.php I simply added
print_r( $cfg ); exit();
just before the 'return' at the end of the file. Then I ran composer phan to invoke it from my shell and display the object variable.
Array
(
    [backward_compatibility_checks] => 
    [parent_constructor_required] => Array
        (
        )

    [quick_mode] => 
    [analyze_signature_compatibility] => 1
    [ignore_undeclared_variables_in_global_scope] => 1
    [read_type_annotations] => 1
    [disable_suppression] => 
    [dump_ast] => 
    [dump_signatures_file] => 
    [processes] => 1
    [whitelist_issue_types] => Array
        (
        )

    [markdown_issue_messages] => 
    [generic_types_enabled] => 1
    [plugins] => Array
        (
            [0] => PregRegexCheckerPlugin
            [1] => UnusedSuppressionPlugin
            [2] => DuplicateExpressionPlugin
            [3] => LoopVariableReusePlugin
            [4] => RedundantAssignmentPlugin
            [5] => UnreachableCodePlugin
            [6] => SimplifyExpressionPlugin
            [7] => DuplicateArrayKeyPlugin
            [8] => UseReturnValuePlugin
            [9] => AddNeverReturnTypePlugin
            [10] => /opt/htdocs/mediawiki/vendor/mediawiki/mediawiki-phan-config/src/../../phan-taint-check-plugin/MediaWikiSecurityCheckPlugin.php
        )

    [plugin_config] => Array
        (
        )

    [file_list] => Array
        (
            [0] => .phan/stubs/password.php
            [1] => .phan/stubs/Socket.php
            [2] => .phan/stubs/WeakMap.php
            [3] => includes/Defines.php
            [4] => tests/phpunit/MediaWikiIntegrationTestCase.php
            [5] => tests/phpunit/includes/TestUser.php
        )

    [exclude_file_list] => Array
        (
            [0] => vendor/squizlabs/php_codesniffer/src/Standards/PSR2/Tests/Methods/MethodDeclarationUnitTest.inc
            [1] => vendor/php-parallel-lint/php-parallel-lint/src/polyfill.php
        )

    [exclude_analysis_directory_list] => Array
        (
            [0] => vendor/
            [1] => .phan/
            [2] => tests/phpunit/
            [3] => includes/config-vars.php
            [4] => includes/composer/
            [5] => maintenance/language/
            [6] => includes/libs/jsminplus.php
            [7] => includes/libs/objectcache/utils/MemcachedClient.php
            [8] => includes/PHPVersionCheck.php
        )

    [exclude_file_regex] => @vendor/((composer/installers|php-parallel-lint/php-console-color|php-parallel-lint/php-console-highlighter|php-parallel-lint/php-parallel-lint|mediawiki/mediawiki-codesniffer|microsoft/tolerant-php-parser|phan/phan|phpunit/php-code-coverage|squizlabs/php_codesniffer|[^/]+/[^/]+/\.phan)|.*/[Tt]ests?)/@
    [minimum_severity] => 0
    [allow_missing_properties] => 
    [null_casts_as_any_type] => 
    [scalar_implicit_cast] => 
    [dead_code_detection] => 
    [dead_code_detection_prefer_false_negative] => 1
    [progress_bar] => 
    [enable_class_alias_support] => 
    [redundant_condition_detection] => 1
    [minimum_target_php_version] => 7.4.3
    [target_php_version] => 8.1
    [directory_list] => Array
        (
            [0] => includes/
            [1] => languages/
            [2] => maintenance/
            [3] => mw-config/
            [4] => resources/
            [5] => vendor/
            [6] => tests/common/
            [7] => tests/parser/
            [8] => tests/phpunit/mocks/
        )

    [suppress_issue_types] => Array
        (
            [0] => PhanDeprecatedFunction
            [1] => PhanDeprecatedClass
            [2] => PhanDeprecatedClassConstant
            [3] => PhanDeprecatedFunctionInternal
            [4] => PhanDeprecatedInterface
            [5] => PhanDeprecatedProperty
            [6] => PhanDeprecatedTrait
            [7] => PhanUnreferencedUseNormal
            [8] => PhanUnreferencedUseFunction
            [9] => PhanUnreferencedUseConstant
            [10] => PhanDuplicateUseNormal
            [11] => PhanDuplicateUseFunction
            [12] => PhanDuplicateUseConstant
            [13] => PhanUseNormalNoEffect
            [14] => PhanUseNormalNamespacedNoEffect
            [15] => PhanUseFunctionNoEffect
            [16] => PhanUseConstantNoEffect
            [17] => PhanDeprecatedCaseInsensitiveDefine
            [18] => PhanAccessClassConstantInternal
            [19] => PhanAccessClassInternal
            [20] => PhanAccessConstantInternal
            [21] => PhanAccessMethodInternal
            [22] => PhanAccessPropertyInternal
            [23] => PhanParamNameIndicatingUnused
            [24] => PhanParamNameIndicatingUnusedInClosure
            [25] => PhanProvidingUnusedParameter
            [26] => PhanPluginMixedKeyNoKey
            [27] => SecurityCheck-LikelyFalsePositive
            [28] => SecurityCheck-PHPSerializeInjection
            [29] => PhanPluginDuplicateExpressionAssignmentOperation
            [30] => PhanPluginDuplicateExpressionAssignmentOperation
        )

    [globals_type_map] => Array
        (
            [wgContLang] => \Language
            [wgParser] => \Parser
            [wgTitle] => \Title
            [wgMemc] => \BagOStuff
            [wgUser] => \User
            [wgConf] => \SiteConfiguration
            [wgLang] => \Language
            [wgOut] => OutputPage
            [wgRequest] => \WebRequest
            [IP] => string
            [wgGalleryOptions] => array
            [wgDummyLanguageCodes] => string[]
            [wgNamespaceProtection] => array<int,string|string[]>
            [wgNamespaceAliases] => array<string,int>
            [wgLockManagers] => array[]
            [wgForeignFileRepos] => array[]
            [wgDefaultUserOptions] => array
            [wgSkipSkins] => string[]
            [wgLogTypes] => string[]
            [wgLogNames] => array<string,string>
            [wgLogHeaders] => array<string,string>
            [wgLogActionsHandlers] => array<string,class-string>
            [wgPasswordPolicy] => array<string,array<string,string|array>>
            [wgVirtualRestConfig] => array<string,array>
            [wgWANObjectCaches] => array[]
            [wgLocalInterwikis] => string[]
            [wgDebugLogGroups] => string|false|array{destination:string,sample?:int,level:int}
            [wgCookiePrefix] => string|false
            [wgExtraNamespaces] => string[]
        )

    [analyzed_file_extensions] => Array
        (
            [0] => php
            [1] => inc
        )

    [autoload_internal_extension_signatures] => Array
        (
            [dom] => .phan/internal_stubs/dom.phan_php
            [excimer] => .phan/internal_stubs/excimer.php
            [imagick] => .phan/internal_stubs/imagick.phan_php
            [memcached] => .phan/internal_stubs/memcached.phan_php
            [oci8] => .phan/internal_stubs/oci8.phan_php
            [pcntl] => .phan/internal_stubs/pcntl.phan_php
            [pgsql] => .phan/internal_stubs/pgsql.phan_php
            [redis] => .phan/internal_stubs/redis.phan_php
            [sockets] => .phan/internal_stubs/sockets.phan_php
            [sqlsrv] => .phan/internal_stubs/sqlsrv.phan_php
            [tideways] => .phan/internal_stubs/tideways.phan_php
            [wikidiff2] => .phan/internal_stubs/wikidiff.php
        )

)




Phan has a ton of plugins - but their usage in the MediaWiki configuration is not mentioned or described anywhere (that I could find). I found a list in 'base-config-functions' mediawiki-tools-phan/blob/master/src/base-config-functions.php#L68, but why you need to read the entire source code to "reverse engineer" what is happening? It's not fun. So, again, refer to the fully parsed configuration in the collapsed section above.

Phan Plugins[edit | edit source]

https://github.com/phan/phan/tree/v5/.phan/plugins#2-general-use-plugins

The writing plugins guide also describes how they work.

Check code for compatibility with a particular PHP version[edit | edit source]

One major reason I wanted to use phan was to upgrade a whole codebase, and check its compatibility with an upgraded PHP (moving from 7.4 to 8.1). There is at least one caveat to doing this: You should be running php 8.1 in order to check code for compatibility with 8.1 You can use phan with the config setting of target_php_version[3]

Phan with VSCode[edit | edit source]

https://github.com/phan/phan/wiki/Editor-Support supposed to just work (with the plugin). But after I installed the plugin, it was not producing any hint of working or any errors.

Phan checks[edit | edit source]

There are over 1,000 things that phan tests its own code against

https://github.com/phan/phan/tree/v5/tests

https://github.com/phan/phan/tree/v5/tests/files/src


References[edit source]