Phan comes with a mechanism for adding plugins for your code base. Plugins have hooks for analyzing every node in the AST for every file, classes, methods and functions. (As well as analyzing param types or return types pf functions when they are invoked. And analyzing functions/constants/classes/class elements that Phan will analyze.)
Plugin code lives in your repo and is referenced from your phan config file, but runs in the Phan execution environment with access to all Phan APIs.
PluginV3
is the current version of the plugin system.
The system is designed to be extensible and as efficient as possible. (e.g. plugins would only be invoked for the functionality they implement)
The constant \Phan\Config::PHAN_PLUGIN_VERSION
may optionally be used by plugin files designed for backwards compatibility.
version_compare
may be used to check if the current plugin system is >= the version where a given Capability
was introduced or changed.
To create a plugin, you'll need to
- Create a plugin file for which the last line returns an instance of a class extending
\Phan\PluginV3
, and implementing one or more of theCapability
interfaces - Add a reference to the file in
.phan/config.php
under theplugins
array.
Phan contains an example plugin named DemoPlugin that is referenced from Phan's .phan/config.php file.
A more meaningful real-world example is given in DollarDollarPlugin which checks to make sure there are no variables of the form $$var
in Phan's code base.
You may wish to base your plugin on a plugin performing a similar task. (list of plugins)
- Additionally, you may wish to base code on other functionality that Phan implements internally as plugins
A plugin file returns an instance of a class extending \Phan\PluginV3 and should implement at least one of the below Capability interfaces (The most up to date documentation is found in \Phan\PluginV3).
- \Phan\PluginV3\PostAnalyzeNodeCapability
to analyze a subset of node kinds after child nodes of that node are analyzed. (e.g. DuplicateArrayKeyPlugin uses this for nodes of the kind
AST_ARRAY
) - \Phan\PluginV3\AnalyzeFunctionCallCapability
can be used to run additional checks of the parameters of function or method invocations. (E.g. PregRegexChecker does this for
preg_match
, etc to check regex uses) - \Phan\PluginV3\ReturnTypeOverrideCapability
can be used to manipulate the return types of a function or method call based on its arguments and other information.
(E.g. Phan uses this for
json_decode
,var_export
, etc - \Phan\PluginV3\AnalyzeClassCapability will call a plugin to analyze every class declaration. (e.g. UnusedSuppressionPlugin uses this to check if suppression doc comments on classes are used)
- \Phan\PluginV3\AnalyzeMethodCapability will call a plugin to analyze every method declaration. (e.g. AlwaysReturnPlugin uses this to check if a method is guaranteed to return)
- \Phan\PluginV3\AnalyzeFunctionCapability will call a plugin to analyze every function declaration. (e.g. AlwaysReturnPlugin uses this to check if a function is guaranteed to return)
- \Phan\PluginV3\AnalyzePropertyCapability will call a plugin to analyze every property declaration. (e.g. UnusedSuppressionPlugin uses this to check if suppression doc comments on a property are used)
- \Phan\PluginV3\FinalizeProcessCapability
will call
finalize(CodeBase)
once after each analysis process (as in--processes N
) ends. (e.g. UnusedSuppressionPlugin will defer emitting warnings untilfinalize(...)
, which indicates that Phan has analyzed every single file and emitted all possible issues) - \Phan\PluginV3\PreAnalyzeNodeCapability
to analyze a subset of node kinds before child nodes of that node are analyzed. (e.g. DuplicateArrayKeyPlugin uses this for nodes of the kind
AST_ARRAY
) Most plugins will use PostAnalyzeNodeCapability instead of PreAnalyzeNodeCapability. - \Phan\PluginV3\SuppressionCapability
can be used to suppress issues based on custom logic.
(e.g.
BuiltinSuppressionPlugin
implements@phan-suppress-next-line
, etc)
As the method names suggest, they analyze (Or return the class name of a Visitor that will analyze) AST nodes, classes, methods and functions; and pre-analyze AST nodes, respectively.
When issues are found, they can be emitted to the log via a call to emitIssue
.
$this->emitIssue(
$code_base,
$context,
'PhanPluginMyPluginType',
"Issue message template associated with the issue.",
[] // optional template args
);
where $code_base
is the CodeBase
object passed to your hook, $context
is the context in which the issue is found (such as the $context
passed to the hook, or from $class->getContext()
, $method->getContext()
or $function->getContext()
, a name for the issue type (allowing it to be suppressed via @suppress) and the message to emit to the user.
The emitted issues may also have format strings (same as recent releases of Phan with V1 support), and the same types of format strings as \Phan\Issue
(E.g. in \UnusedSuppressionPlugin
$this->emitIssue(
$code_base,
$element->getContext(),
'UnusedSuppression',
"Element {FUNCTIONLIKE} suppresses issue {ISSUETYPE} but does not use it", // This type of format string lets ./phan --color colorize the output
[(string)$element->getFQSEN(), $issue_type]
);
A shorthand was added to emit issues from visitors such as PluginAwareAnalysisVisitor or PluginAwarePreAnalysisVisitor, via $this->emit(issue type, format string, [args...], [...])
(Implicitly uses global codebase and current context),
or $this->emitPluginIssue(CodeBase, Context, issue type, format string, [args...], [...])
. See InvalidVariableIssetVisitor
for an example of this.
When writing plugins, you'll likely need to understand a few concepts. The following contains some material that may be useful. You can learn more from the Developer's Guide to Phan
A Node
is an AST node returned from the php-ast PHP extension. You can read more about its interface in its README. You'll also find many references to Node
that can be copied throughout the Phan code base.
The Clazz class contains things you'll need to know about a class such as its FQSEN (fully-qualified structural element name), name, type, context, and flags such as isAbstract
, isInterface
, isTrait
, etc.
The Method class contains things you'll need to know about methods such as its FQSEN, name, parameters, return type, etc..
Similarly, the Func class contains things you'll need to know about functions.
A Context is a thing defined for every line of code that tells you which file you're in, which line you're on, which class you're in, which methods, function or closure you're in and the Scope that is available to you which contains all local and global variables.
A UnionType is a set of Types defined for an object such as int|string|DateTime|null
. You can read more about UnionTypes here.
You'll likely find yourself getting types frequently via a call to UnionTypeVisitor::unionTypeFromNode(...)
such as with:
$union_type = UnionTypeVisitor::unionTypeFromNode($code_base, $context, $node);
which provides you with the union type of the statement defined by the AST node $node
.
A ContextNode contains a lot of useful functionality, such as locating the definition of an element from a reference (class, function-like, property, etc) in a Context,
or getting an equivalent PHP value for a given Node.
(PregRegexCheckerPlugin
uses that).
A CodeBase is a thing containing a mapping from all FQSENs (fully qualified structural element names) to their associated objects (Classes, Methods, Functions, Properties, Constants).
You can use the CodeBase to look up info on any objects you find references to.
More typically, you'll need to keep the $code_base
around to pass it to all of Phan's methods.