Writing Plugins for Phan

Tyson Andre edited this page Aug 12, 2018 · 18 revisions

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.

Plugin V2

PluginV2 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. If it is defined(), then V2 of the plugin system is supported. version_compare may be used to check if the current plugin system is >= the version where a given Capability was introduced.

Creating a Plugin (V2)

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\PluginV2, and implementing one or more of the Capability interfaces
  • Add a reference to the file in .phan/config.php under the plugins 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 variable 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)

How Plugins Work (V2)

A plugin file returns an instance of a class extending \Phan\PluginV2 and should implement at least one of the below Capability interfaces (The most up to date documentation is found in \Phan\PluginV2).

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.

Reference Material

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

Node

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.

Clazz

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.

Method

The Method class contains things you'll need to know about methods such as its FQSEN, name, parameters, return type, etc..

Func

Similarly, the Func class contains things you'll need to know about functions.

Context

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.

UnionType

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.

ContextNode

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).

CodeBase

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.

You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.