php 51 lines · 6 steps

Building a safe HTML escaper in PHP

A small class that centralizes output escaping to prevent XSS across text, attributes, and URLs.

Explained by highlit
1<?php
2 
3namespace App\View;
4 
5final class HtmlEscaper
6{
7 private const FLAGS = ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML5;
8 
9 public function __construct(private readonly string $encoding = 'UTF-8')
10 {
11 }
12 
13 public function escape(?string $value): string
14 {
15 if ($value === null || $value === '') {
16 return '';
17 }
18 
19 return htmlspecialchars($value, self::FLAGS, $this->encoding);
20 }
21 
22 public function escapeAttribute(?string $value): string
23 {
24 return $this->escape($value);
25 }
26 
27 public function escapeUrl(?string $url): string
28 {
29 if ($url === null) {
30 return '';
31 }
32 
33 $scheme = parse_url($url, PHP_URL_SCHEME);
34 
35 if ($scheme !== null && !in_array(strtolower($scheme), ['http', 'https', 'mailto'], true)) {
36 return '#';
37 }
38 
39 return $this->escape($url);
40 }
41 
42 public function renderComment(string $author, string $body, string $profileUrl): string
43 {
44 return sprintf(
45 '<article class="comment"><a href="%s">%s</a><p>%s</p></article>',
46 $this->escapeUrl($profileUrl),
47 $this->escape($author),
48 nl2br($this->escape($body), false)
49 );
50 }
51}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Centralizing escaping in one class makes safe output the default and the unsafe path the exception.
  2. 2Different output contexts (text, attribute, URL) need different defenses, not one blanket escape.
  3. 3Validating a URL scheme blocks javascript: and data: payloads that plain HTML escaping would let through.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Building a safe HTML escaper in PHP — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code