@variable: When the placeholder replacement value is:
A string, the replaced value in the returned string will be sanitized using \Drupal\Component\Utility\Html::escape().
A MarkupInterface object, the replaced value in the returned string will not be sanitized.
A MarkupInterface object cast to a string, the replaced value in the returned string be forcibly sanitized using \Drupal\Component\Utility\Html::escape().
$this->placeholderFormat('This will force HTML-escaping of the replacement value: @text', ['@text' => (string) $safe_string_interface_object));
Use this placeholder as the default choice for anything displayed on the site, but not within HTML attributes, JavaScript, or CSS. Doing so is a security risk.
%variable: Use when the replacement value is to be wrapped in <em> tags. A call like:
$string = "%output_text";
$arguments = [
'%output_text' => 'text output here.',
];
$this
->placeholderFormat($string, $arguments);
makes the following HTML code:
<em class="placeholder">text output here.</em>
As with @variable, do not use this within HTML attributes, JavaScript, or CSS. Doing so is a security risk.
:variable: Return value is escaped with \Drupal\Component\Utility\Html::escape() and filtered for dangerous protocols using UrlHelper::stripDangerousProtocols(). Use this when using the "href" attribute, ensuring the attribute value is always wrapped in quotes:
// Secure (with quotes):
$this
->placeholderFormat('<a href=":url">@variable</a>', [
':url' => $url,
'@variable' => $variable,
]);
// Insecure (without quotes):
$this
->placeholderFormat('<a href=:url>@variable</a>', [
':url' => $url,
'@variable' => $variable,
]);
When ":variable" comes from arbitrary user input, the result is secure, but not guaranteed to be a valid URL (which means the resulting output could fail HTML validation). To guarantee a valid URL, use Url::fromUri($user_input)->toString() (which either throws an exception or returns a well-formed URL) before passing the result into a ":variable" placeholder.