setasign / php-syntax-highlighter
A PHP syntax highlighter, written in PHP, that automatically links classes, methods, class constants and functions to their documentation.
Package info
github.com/Setasign/PHPSyntaxHighlighter
Language:HTML
pkg:composer/setasign/php-syntax-highlighter
Requires
- php: ^8.5
- nikic/php-parser: ^5.8
Requires (Dev)
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
A PHP syntax highlighter, written in PHP, that automatically links classes, methods, class constants and functions to their documentation (the official PHP manual by default), with support for linking to your own, additional manuals as well. The PHP Syntax Highlighter will only highlight PHP code (and there is no intent to add more languages).
Motivation
Our main business at Setasign are the PHP libraries SetaPDF and FPDI. For these products we have our own manual with an api reference and executable code examples. We wanted to highlight the code in the examples and be able to link the code to the api reference (like in your IDE). There are many code highlighter out there but most of them prefer to highlight as many languages as possible instead of specialized features like this to link to the api reference.
How it works
The source code is parsed into an AST with nikic/php-parser. A visitor
walks that AST once to figure out, for every relevant position in the source, which URL (if any) it should link
to. Afterward, the original source is tokenized with PHP's native PhpToken::tokenize() and rendered token by
token as <span> elements, wrapping the collected positions in <a> tags. Using the AST only for finding
links (and the tokenizer for the actual output) keeps the highlighted HTML a deterministic, 1:1 representation
of the original source.
Requirements
- PHP >= 8.5
- nikic/php-parser ^5.8
Installation
composer require setasign/php-syntax-highlighter
Usage
use setasign\PhpSyntaxHighlighter\PhpSyntaxHighlighter; $highlighter = new PhpSyntaxHighlighter(); $html = $highlighter->highlight(<<<'PHP' $date = new DateTime('now'); echo $date->format(DateTime::ATOM); PHP);
An opening <?php tag is optional.
The returned HTML wraps every token in a <span class="php-token php-token-*"> and adds an <a href="..." class="manual-link" target="_blank"> around every linkable class, method, class constant and function name.
Styling
Every token has the CSS class php-token and CSS class related to its type e.g. php-token-t-open-tag.
The full list of tokens can be found here.
The token class name is simply generated by lowercase and replacing _ with -.
If the token is a single character (i.e.: ;, ., >, !, etc...) its getting the CSS class php-token-char.
Every link has the CSS class manual-link.
A very simple example styling can be found in example/example-style.css.
Linking to additional manuals
Links are resolved through a LinkBuilder, which asks a prioritized list of manuals each implementing
Manuals\ManualLinkBuilderInterface whether they know a given class, method, class constant or function.
The built-in Manuals\PhpManualByReflection links everything that PHP's Reflection API reports as internal to
the official PHP manual. You can register additional manuals, for example to link your own library's classes to
your own documentation:
use setasign\PhpSyntaxHighlighter\PhpSyntaxHighlighter; use setasign\PhpSyntaxHighlighter\Manuals\ManualLinkBuilderInterface; class MyLibraryManual implements ManualLinkBuilderInterface { // ...implement getClassLink(), getClassMethodLink(), getClassConstantLink(), // getFunctionLink(), getFunctionReturnType() and getMethodReturnType() } $highlighter = new PhpSyntaxHighlighter(); $highlighter->linkBuilder->addManual(new MyLibraryManual());
Manuals are asked in the order they were added; the first manual that returns a non-null result wins.
getFunctionReturnType()/getMethodReturnType() should return null when the function/method is unknown to a
manual (as opposed to an empty array, which means "known, but no linkable return type"), so that the next
manual in line still gets a chance.
Type hints
The highlighter tries to infer the type of every variable, property and method/function return value from the
code itself, including classes, interfaces, traits and enums that are declared in the highlighted source itself
(e.g. following an extends/implements/use TraitName; chain). That inference isn't always possible though
for example a parameter without a type declaration, or a variable assigned from something the highlighter can't
follow. For these cases, highlight() accepts an optional second argument to explicitly tell it about types:
$highlighter->highlight($code, [ 'variables' => [ 'date' => 'DateTime', // $date is a DateTime '$zone' => ['DateTimeZone'], // the leading '$' is optional; multiple candidate types are allowed ], 'classes' => [ 'Blub' => 'Vendor\Blub', // resolve an otherwise-unresolvable class name ], 'functions' => [ 'blub_create' => 'date_create', // treat blub_create() like date_create() for linking purposes ], ]);
Type hints ignore scoping and are always linked to the given type, regardless of where in the code the variable/class/function is referenced.
Known limitations
- Global constants (as opposed to class constants) are not linked, and
use constimports are ignored. - Union and intersection types are treated the same way: every type contained in them is considered a candidate, without honoring the "any of" vs. "all of" semantics.
- Typed arrays are currently not supported
- Type inference for classes, interfaces, traits and enums declared in the highlighted source only works when the declaration comes before its use, in source order. A forward reference (using a class before its declaration) is not resolved and can be worked around with an explicit type hint (see above).