Ein paar Zeilen PHP sollen auf der eigenen Seite farbig erscheinen, so wie im Editor. Dafür muss man keine JavaScript-Bibliothek nachladen, PHP bringt das Syntax-Highlighting selbst mit. PHP highlight_string() nimmt Code als Text, zerlegt ihn und gibt HTML mit einer Farbe für jedes Stück zurück. Viele Antworten im Netz zeigen allerdings eine Ausgabe, die es seit PHP 8.3 nicht mehr gibt, und wer Code darauf aufgebaut hat, merkt es oft erst nach dem Update.
Die Farben holt PHP highlight_string() aus fünf INI-Werten, und sowohl die Farben als auch das erzeugte Markup lassen sich anpassen.
Was macht PHP highlight_string()?
Die Funktion zerlegt den Text in Bausteine wie Schlüsselwörter, Variablen, Strings und Kommentare und setzt um jeden ein <span> mit einer Farbe. Ausgeführt wird dabei nichts. Selbst Code mit einem nicht geschlossenen String oder einem halben function ( { wird ohne Fehlermeldung gefärbt, weil PHP highlight_string() den Code nur zerlegt und nicht auf Gültigkeit prüft. Für eine Syntaxprüfung ist php -l zuständig. HTML im Code wird maskiert: Aus "Anna <b>" wird in der Ausgabe "Anna <b>", ein eingeschleustes Tag landet also als Text auf der Seite.
Ob überhaupt eine Farbe ins Spiel kommt, entscheidet das öffnende PHP-Tag:
flowchart TD
A[PHP-Code als String] --> B{PHP-Tag vorn?}
B -->|nein| C[alles in der HTML-Farbe]
B -->|ja| D[Code in Token zerlegen]
D --> E[Farbe je Token aus INI-Wert]
E --> F[span mit style setzen]
F --> G{return true?}
G -->|ja| H[HTML als String]
G -->|nein| I[direkt ausgeben]
Syntax und Parameter im Original
Neben PHP highlight_string() gibt es die Variante für Dateien. Beide Signaturen, ausgelesen unter PHP 8.4:
highlight_string(
string $string,
bool $return = false
): string|true
highlight_file(
string $filename,
bool $return = false
): string|bool
/* $string PHP code; only code after an
opening <?php tag gets a color
$filename path of the file to highlight
$return true = return the HTML string,
false = print it
returns the HTML string, or true after
printing; highlight_file()
returns false if the file
cannot be read
Syntax highlighting of PHP code with the
colors defined in php.ini. show_source()
is an alias of highlight_file(). */
Der Rückgabetyp string|true stammt aus PHP 8.4, vorher stand dort string|bool. Praktisch ändert das nichts: Ohne zweiten Parameter gibt die Funktion das HTML aus und liefert true.
Ausgeben oder als HTML-String zurückgeben
Der zweite Parameter von PHP highlight_string() entscheidet, wohin das Ergebnis geht. Für eigene Templates ist die Rückgabe fast immer der bessere Weg:
<?php
$code = '<?php echo "Hallo, " . $name; ?>';
highlight_string($code);
/* gibt das HTML direkt aus */
$html = highlight_string($code, true);
/* liefert das HTML als String */
In altem Code steht an dieser Stelle manchmal ob_start() mit ob_get_clean(), um die direkte Ausgabe abzufangen. Wie das funktioniert, zeigt das Tutorial zu Output Buffering, für PHP highlight_string() ist der Umweg aber unnötig.
Bleibt der Code schwarz, beginnt der Text meist nicht mit <?php. Ohne das Tag hält PHP alles für HTML und gibt es in einer einzigen Farbe aus. Erscheint überhaupt nichts, fehlt oft das echo: Mit true kommt das Ergebnis als Rückgabe und wird nicht ausgegeben.
<?php
$schnipsel = '$summe = $a + $b;';
echo highlight_string($schnipsel, true);
/* <pre><code style="color: #000000">
$summe = $a + $b;</code></pre> */
echo highlight_string('<?php ' . $schnipsel, true);
/* jetzt mit farbigen span-Tags */
Wer Schnipsel aus einer Datenbank oder aus Forenbeiträgen anzeigt, ergänzt das Tag deshalb automatisch. Die Kurzform <?= erkennt PHP ebenfalls:
<?php
function farbig(string $code): string
{
$code = ltrim($code);
if (!str_starts_with($code, '<?')) {
$code = "<?php\n" . $code;
}
return highlight_string($code, true);
}
Die HTML-Ausgabe seit PHP 8.3
Mit PHP 8.3 hat sich das HTML geändert, das PHP highlight_string() erzeugt; das Handbuch vermerkt es im Changelog. Für "<?php\n$a = 1;" liefert PHP 8.4 diese Ausgabe, hier zur Lesbarkeit hinter einigen Tags umbrochen. Der Zeilenumbruch nach dem maskierten Tag <?php steht tatsächlich so im HTML:
<pre><code style="color: #000000">
<span style="color: #0000BB"><?php
$a </span><span style="color: #007700">= </span>
<span style="color: #0000BB">1</span>
<span style="color: #007700">;</span></code></pre>
Bis PHP 8.2 sah dieselbe Ausgabe grundlegend anders aus. Gemessen mit den offiziellen Docker-Images für 8.2 und 8.4:
| Merkmal | bis PHP 8.2 | ab PHP 8.3 |
| Hülle | <code><span style="color: #000000"> | <pre><code style="color: #000000"> |
| Zeilenumbruch | <br /> | echter Umbruch im <pre> |
| Leerzeichen | | normales Leerzeichen |
| Tabulator | vier | vier Leerzeichen |
| erste Zeile im Beispiel | <?php<br />$a | <?php und Umbruch |
| Länge der Beispielausgabe oben | 227 Zeichen | 207 Zeichen |
Für eigenen Code hat das zwei Folgen. Wer die Ausgabe an <br /> zerlegt, um Zeilen zu nummerieren, bekommt seit 8.3 ein einziges Stück zurück. Und wer das Ergebnis in ein eigenes <code> mit white-space:nowrap steckt, hat jetzt ein <pre> darin. Beides gehört nach einem Update auf 8.3 oder neuer geprüft.
Farben der PHP-Syntax: die INI-Werte highlight.*
Die Farben von PHP highlight_string() stammen aus fünf Einstellungen. Alle lassen sich zur Laufzeit mit ini_set() ändern, ihre Standardwerte sind in PHP 8.2 bis 8.5 gleich:
| Einstellung | Standard | färbt |
highlight.keyword | #007700 | Schlüsselwörter wie if und echo, Operatoren, Klammern, Semikolon |
highlight.default | #0000BB | Variablen, Funktionsnamen, Zahlen, das Tag <?php |
highlight.string | #DD0000 | Strings und Text in Heredoc |
highlight.comment | #FF8000 | Kommentare |
highlight.html | #000000 | HTML außerhalb von PHP und die Grundfarbe des <code> |
HTML außerhalb von PHP zerlegt die Funktion nicht weiter, es erscheint komplett in der Farbe von highlight.html. Wer HTML und PHP gemischt farbig zeigen will, braucht einen JavaScript-Highlighter. Für ein dunkles Design genügen fünf Zeilen vor dem Aufruf. PHP übernimmt jeden gültigen CSS-Farbwert, auch Namen wie green:
<?php
ini_set('highlight.comment', '#7f8c8d');
ini_set('highlight.default', '#e6e6e6');
ini_set('highlight.html', '#bbbbbb');
ini_set('highlight.keyword', '#f0a35e');
ini_set('highlight.string', '#9fca56');
echo highlight_string($code, true);
Eigene CSS-Klassen statt fester Farben
Die Farben landen als style-Attribut in jedem <span>. Gegen Inline-Styles kommt ein normales Stylesheet nicht an, und ein Wechsel zwischen hellem und dunklem Design wird mühsam. Der Ausweg: eindeutige Platzhalterfarben setzen und sie danach gegen Klassen tauschen.
<?php
function mit_klassen(string $code): string
{
$tausch = [];
$alt = [];
$farben = [
'comment' => '#000001',
'default' => '#000002',
'html' => '#000003',
'keyword' => '#000004',
'string' => '#000005',
];
foreach ($farben as $art => $farbe) {
$alt[$art] = ini_set("highlight.$art", $farbe);
$tausch["style=\"color: $farbe\""] =
"class=\"hl-$art\"";
}
$html = highlight_string($code, true);
foreach ($alt as $art => $wert) {
ini_set("highlight.$art", $wert);
}
return strtr($html, $tausch);
}
Das Ergebnis beginnt mit <pre><code class="hl-html">, und jedes Stück trägt eine Klasse wie hl-keyword. Die Farben stehen danach im Stylesheet, für das dunkle Design etwa in einem Block mit prefers-color-scheme: dark. Die alten Werte stellt die Funktion über den Rückgabewert von ini_set() wieder her. ini_restore() half im Test nicht: Unter PHP 8.2, 8.4 und 8.5 blieb danach die Platzhalterfarbe stehen.
Zeilennummern neben den Code setzen
Eine Option für Zeilennummern kennt PHP highlight_string() nicht. Die naheliegende Idee, die Ausgabe an Zeilenumbrüchen zu zerlegen und jede Zeile einzeln einzupacken, scheitert an den <span>-Tags: Oft beginnt ein Span am Ende einer Zeile und schließt erst in der nächsten. Gemessen fing die zweite Zeile eines kurzen Beispiels mit </span> an. Robuster ist eine eigene Spalte, deren Nummern aus dem Quelltext gezählt werden:
<?php
function mit_zeilen(string $code): string
{
$code = rtrim($code);
$html = highlight_string($code, true);
$anzahl = substr_count($code, "\n") + 1;
$nummern = implode("\n", range(1, $anzahl));
return '<div class="code-zeilen">'
. '<pre class="nr">' . $nummern . '</pre>'
. $html
. '</div>';
}
Mit display:flex auf .code-zeilen stehen beide Blöcke nebeneinander. Weil seit PHP 8.3 auch die Ausgabe in einem <pre> liegt, genügen für beide Spalten dieselbe Schrift und dieselbe Zeilenhöhe im Stylesheet.
highlight_file() und show_source(): Dateien anzeigen
Für ganze Dateien gibt es highlight_file() und den gleichwertigen Alias show_source(). Für denselben Inhalt gleicht die Ausgabe der von PHP highlight_string() Zeichen für Zeichen. Fehlt die Datei, liefert die Funktion false und zwei Warnungen: highlight_file(...): Failed to open stream: No such file or directory und highlight_file(): Failed opening '...' for highlighting. Auf der Kommandozeile erledigt php -s datei.php dasselbe, das Ergebnis entspricht der Ausgabe der jeweiligen PHP-Version.
Sicherheit: Quelltext nur gezielt zeigen
Was highlight_file() anzeigt, steht im Klartext auf der Seite, auch Zugangsdaten. Eine Testdatei mit $pass = 'geheim'; erschien vollständig in der Ausgabe. Ein Dateiname aus $_GET darf deshalb nie direkt durchgereicht werden, sonst zeigt ein ?datei=../config.php die Konfiguration. Sicher ist eine feste Liste erlaubter Dateien:
<?php
$ordner = __DIR__ . '/beispiele';
$erlaubt = ['hallo.php', 'schleife.php'];
$wunsch = $_GET['datei'] ?? '';
$datei = is_string($wunsch) ? basename($wunsch) : '';
if (!in_array($datei, $erlaubt, true)) {
http_response_code(404);
exit('Unbekanntes Beispiel');
}
echo highlight_file("$ordner/$datei", true);
basename() schneidet Pfadangaben wie ../ ab, die Liste sorgt dafür, dass nur bekannte Namen durchkommen. Das is_string() davor fängt Anfragen wie ?datei[]=x ab, die sonst mit basename(): Argument #1 ($path) must be of type string, array given abbrechen würden. Wer auf einem Server gar keine Quelltextanzeige zulassen will, trägt highlight_file, show_source und highlight_string in disable_functions ein. Seit PHP 8.0 entfernt die Einstellung die Funktion vollständig, ein Aufruf endet dann in Call to undefined function highlight_string(). Wer diese Meldung auf einem fremden Server sieht, hat dort eine abgeschaltete Funktion vor sich. Die Einstellung wirkt laut Handbuch nur aus der php.ini, nicht über ini_set(), und gilt dort ausdrücklich nicht als ausreichende Absicherung für Shared Hosting.
PHP highlight_string() oder JavaScript-Highlighter?
Die eingebaute Funktion kennt nur PHP, setzt feste Inline-Farben und liefert weder Zeilennummern noch Themes. Bibliotheken wie Prism oder highlight.js färben dagegen im Browser per JavaScript und kennen viele Sprachen. Auch php-space.info nutzt Prism für die Codeblöcke seiner Tutorials.
| Frage | highlight_string() | Prism, highlight.js |
| Sprachen | nur PHP | PHP, HTML, SQL, JavaScript und viele mehr |
| JavaScript nötig | nein | ja |
| Farben anpassen | ini_set() oder Klassen-Tausch | Themes per CSS |
| Zeilennummern | selbst bauen | als Erweiterung vorhanden |
Wer eigene Klassen für jede Art von Baustein braucht, ohne Platzhalterfarben, geht eine Ebene tiefer zum PHP Tokenizer. PhpToken::tokenize() liefert jedes Stück mit Namen, daraus wird direkt eine Klasse:
<?php
$html = '';
foreach (PhpToken::tokenize($code) as $t) {
$name = $t->getTokenName();
$klasse = str_starts_with($name, 'T_')
? strtolower($name)
: 't_zeichen';
$html .= '<span class="' . $klasse . '">'
. htmlspecialchars($t->text)
. '</span>';
}
echo '<pre><code>' . $html . '</code></pre>';
Aus echo wird so <span class="t_echo">, aus einem Kommentar t_comment. Das ist mehr Arbeit, dafür bestimmt das Stylesheet allein über das Aussehen.
Fazit
Für ein paar farbige Zeilen PHP auf der eigenen Seite reicht PHP highlight_string() völlig. Dazu gehören das öffnende <?php im Text und ein echo, sobald der zweite Parameter auf true steht. Seit PHP 8.3 kommt das Ergebnis als <pre><code> mit echten Zeilenumbrüchen, alter Code mit <br />-Logik braucht deshalb eine Durchsicht.
Farben ändert ini_set(), Klassen entstehen über Platzhalter oder über den Tokenizer. Bei highlight_file() entscheidet die Whitelist darüber, ob ein Beispielordner gezeigt wird oder die Konfiguration mit dem Datenbankpasswort.