Data Types And Standard Library
Strings
Strings are text values. PHP applications use strings for names, emails, slugs, URLs, search terms, template output, log messages, JSON fields, file paths, database values, and messages shown to users.
Good string handling is about boundaries. Clean input when it enters the application, keep internal values predictable, and escape output for the place it is going. A string that is valid for one purpose is not automatically safe for another. A display name, an HTML attribute, a SQL value, a URL segment, and a shell argument all need different treatment.
This lesson focuses on everyday string work in plain PHP: quoting, interpolation, trimming, searching, replacing, splitting, joining, length, empty values, and HTML output. Later lessons go deeper into Unicode, regular expressions, files, JSON, and web security.
Quoting and interpolation
Single-quoted strings are mostly literal. They do not interpolate variables. Double-quoted strings do interpolate variables and recognize more escape sequences. Both forms create strings; the choice is about readability and intent.
<?php
declare(strict_types=1);
$name = 'Sam';
echo 'Hello $name' . PHP_EOL;
echo "Hello {$name}" . PHP_EOL;
// Prints:
// Hello $name
// Hello Sam
Use braces when interpolating variables inside longer text. They make the boundary obvious and avoid surprises when adjacent characters could be read as part of the variable name.
<?php
declare(strict_types=1);
$status = 'paid';
$id = 42;
echo "Invoice {$id} is {$status}." . PHP_EOL;
// Prints:
// Invoice 42 is paid.
For complex output, concatenation, sprintf(), or a template can be clearer than a long interpolated string. Interpolation is best when the surrounding text is simple.
Trimming input at the boundary
Input often contains accidental spaces, tabs, or newlines. Use trim() at the boundary before validation or storage when surrounding whitespace is not meaningful.
<?php
declare(strict_types=1);
$name = trim(' Nia Stone ');
$initial = substr($name, 0, 1);
printf("Hello %s, your initial is %s\n", $name, $initial);
// Prints:
// Hello Nia Stone, your initial is N
Trimming should match the field. A display name usually should not start or end with spaces. A password should not be silently trimmed unless the product has a very explicit rule, because a space can be part of the secret the user chose. A free-form message may preserve internal and surrounding whitespace. Treat trimming as a data rule, not as a reflex applied everywhere.
After trimming, validate the result. Do not check the original input for emptiness and then store a trimmed value, because a string containing only spaces would pass the first check and become empty later.
<?php
declare(strict_types=1);
function requireDisplayName(string $name): string
{
$name = trim($name);
if ($name === '') {
throw new InvalidArgumentException('Display name is required.');
}
return $name;
}
echo requireDisplayName(' Sam ') . PHP_EOL;
// Prints:
// Sam
Empty strings and falsy values
PHP has several values that are considered falsy, including '', '0', 0, 0.0, false, null, and an empty array. For required text input, be specific. If the rule is "after trimming, the value must not be the empty string", compare with === ''.
<?php
declare(strict_types=1);
function isMissingText(string $value): bool
{
return trim($value) === '';
}
var_dump(isMissingText(' '));
var_dump(isMissingText('0'));
// Prints:
// bool(true)
// bool(false)
The string '0' can be a valid code, apartment number, search term, or form value. A loose check such as if (! $value) can reject it by accident. Strict comparisons make the data rule visible.
Searching strings
Use the intent-specific string search functions for common checks. str_contains() answers "does this text appear anywhere?" str_starts_with() answers "does this prefix appear at the beginning?" str_ends_with() answers "does this suffix appear at the end?"
<?php
declare(strict_types=1);
$email = 'sam@example.com';
var_dump(str_contains($email, '@'));
var_dump(str_ends_with($email, '.com'));
var_dump(str_starts_with($email, 'admin'));
// Prints:
// bool(true)
// bool(true)
// bool(false)
These functions are clearer than older strpos() checks. If you do use strpos(), remember that a match at the beginning returns integer 0, which is different from boolean false. Always compare with !== false.
<?php
declare(strict_types=1);
$path = '/admin/reports';
if (strpos($path, '/admin') !== false) {
echo 'Admin area' . PHP_EOL;
}
// Prints:
// Admin area
Search functions are case-sensitive unless the function name or your code says otherwise. Convert both sides when case-insensitive behavior is intended, and be careful with Unicode case rules. The Unicode lesson covers multibyte-aware choices.
Replacing text
Use str_replace() for simple literal replacement. It does not interpret patterns, so it is a good fit when you know the exact text to replace.
<?php
declare(strict_types=1);
$title = 'PHP strings basics';
$slug = strtolower(str_replace(' ', '-', trim($title)));
echo $slug . PHP_EOL;
// Prints:
// php-strings-basics
That slug example is intentionally simple. Production slugging often needs rules for punctuation, repeated separators, accents, reserved words, length limits, and uniqueness. Do not present a teaching example as a complete URL policy.
Replacement is also useful for normalizing predictable values.
<?php
declare(strict_types=1);
$phone = '(555) 010-9000';
$digits = str_replace(['(', ')', ' ', '-'], '', $phone);
echo $digits . PHP_EOL;
// Prints:
// 5550109000
This removes a small set of known formatting characters. If the accepted grammar is more complex, validate it explicitly instead of trying to fix every possible input.
Splitting and joining
Use explode() when a string is separated by a literal delimiter. Use implode() when you need to join an array of strings.
<?php
declare(strict_types=1);
$line = 'red,green,blue';
$parts = explode(',', $line);
echo $parts[1] . PHP_EOL;
echo implode(' | ', $parts) . PHP_EOL;
// Prints:
// green
// red | green | blue
Splitting a string is not the same as parsing a file format. CSV, URLs, query strings, JSON, MIME headers, and shell commands have escaping and quoting rules. Use the format-specific tool for those cases. For example, do not parse CSV by calling explode(',', $line) once fields can contain quoted commas.
After splitting user input, trim and validate each part according to the field rule.
<?php
declare(strict_types=1);
$rawTags = ' php, strings, validation ';
$tags = [];
foreach (explode(',', $rawTags) as $tag) {
$tag = trim($tag);
if ($tag !== '') {
$tags[] = $tag;
}
}
echo implode(', ', $tags) . PHP_EOL;
// Prints:
// php, strings, validation
That loop deliberately drops empty tag values. In another domain, an empty field might be an error instead. Make the rule explicit.
Length, bytes, and characters
strlen() returns the number of bytes in a string. For plain ASCII text, bytes and characters line up. For UTF-8 text containing many non-English characters, emoji, or combining marks, byte length and user-perceived character count can differ.
<?php
declare(strict_types=1);
echo strlen('Sam') . PHP_EOL;
// Prints:
// 3
Use strlen() for byte-oriented work such as payload limits, binary strings, hashes, and protocol fields that define byte counts. Use multibyte-aware functions such as mb_strlen() when the application needs a character-oriented rule and the mbstring extension is available.
<?php
declare(strict_types=1);
if (function_exists('mb_strlen')) {
echo mb_strlen('Sam', 'UTF-8') . PHP_EOL;
}
// Prints:
// 3
This course has a separate Unicode and multibyte lesson. For now, remember the distinction: a byte limit protects storage and protocols, while a character rule is closer to what a user sees. Many systems need both.
Formatting strings
Concatenation is fine for short pieces of text. sprintf() is useful when a message has several values, needs consistent number formatting, or would become hard to read with repeated dots.
<?php
declare(strict_types=1);
$name = 'Nia';
$count = 3;
echo sprintf('%s has %d unread messages.', $name, $count) . PHP_EOL;
// Prints:
// Nia has 3 unread messages.
Formatting does not escape output. A formatted string containing user input is still just a string. If it is going into HTML, escape it for HTML. If it is going into JSON, encode it as JSON. If it is going into a database query, use bound parameters instead of building SQL text.
Escape output for the target context
Cleaning a string is not the same as making it safe for HTML. Escape at the output boundary, as close as possible to the place where the string enters the output context.
<?php
declare(strict_types=1);
$name = '<Sam>';
echo htmlspecialchars($name, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . PHP_EOL;
// Prints:
// <Sam>
htmlspecialchars() is appropriate for normal HTML text and attribute values when used with the right flags and encoding. Other contexts need different tools. SQL values should be passed through prepared statements. JSON output should be produced with json_encode(). URL query parameters should be encoded with URL functions. Shell arguments require shell-specific escaping and should be avoided when a safer process API is available.
Keep validation, normalization, and escaping separate:
- validation decides whether a value is acceptable;
- normalization converts accepted input into a predictable internal shape;
- escaping adapts a value for one output context.
If you HTML-escape before storing a display name, every later consumer inherits an HTML-specific representation. That can cause double-escaping in templates and broken values in emails, logs, JSON, or APIs. Store the clean value. Escape when rendering.
Testing string code
String helpers should be tested with normal input and boundary input. Include leading and trailing spaces, the empty string, strings containing only spaces, the string '0', expected punctuation, unexpected punctuation, values at length limits, and values containing characters that need output escaping.
For display-name style helpers, useful assertions include:
- accepted names are trimmed once;
- empty names after trimming are rejected;
'0'is not rejected merely because it is falsy;- HTML-sensitive characters remain part of the stored clean value;
- HTML escaping happens only when rendering;
- repeated calls do not double-escape already-rendered output.
Tests should also describe the domain rule. A username, display name, article title, password, tag list, and import identifier should not all share one generic "sanitize string" helper. They have different rules and deserve names that make those rules reviewable.
Review checklist
Before approving string-handling code, identify the source of the string, the internal representation, and every output context. Check whether trimming is intentional for that field. Confirm required values are tested with trim($value) === '' rather than a broad falsy check. Prefer str_contains(), str_starts_with(), and str_ends_with() for simple searches. Use str_replace() only for literal replacement, and use parsers for structured formats.
For security-sensitive output, look for context-specific escaping at the boundary. Do not accept a helper named sanitize() without reading what it actually does. Good string code is explicit: it says what field it is handling, what shape is allowed, what is normalized, and where output escaping occurs.
Naming string helpers clearly
Generic helper names such as cleanString(), sanitize(), or makeSafe() hide important policy. A reviewer cannot tell whether the helper trims whitespace, rejects control characters, lowercases text, escapes HTML, removes punctuation, or prepares a database value. Prefer names that include the field or output context, such as cleanDisplayName(), normalizeTags(), renderHtmlText(), or buildSearchQuery().
Small, specific helpers are easier to test and safer to reuse. If two fields have different rules, give them different helpers even when their first implementations look similar. The future change is the reason: article titles, profile summaries, tags, and passwords tend to diverge as the product becomes more precise.
What to remember
Trim input only when the field rule calls for it. Validate required strings after trimming. Use strict comparisons so '0' is not confused with an empty value. Choose clear search, replacement, splitting, and formatting functions. Know when byte length is different from character length. Store clean internal strings, and escape them for the exact output context at the boundary.
Before moving on, make sure you can build a safe display name from messy input without confusing trimming, validation, normalization, and HTML escaping.
Practice
Task: Prepare a safe display name
Create a safe display name helper for HTML output.
Requirements
- Use strict types.
- Trim the input.
- Reject an empty name after trimming.
- Return the cleaned name from one function.
- Escape the cleaned name for HTML output at the output boundary.
- Include one normal case and one empty-name case.
Show solution
<?php
declare(strict_types=1);
function cleanDisplayName(string $name): string
{
$name = trim($name);
if ($name === '') {
throw new InvalidArgumentException('Name is required.');
}
return $name;
}
function escapeHtml(string $value): string
{
return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
$name = cleanDisplayName(' <Nia> ');
echo 'Hello ' . escapeHtml($name) . PHP_EOL;
try {
cleanDisplayName(' ');
} catch (InvalidArgumentException $exception) {
echo $exception->getMessage() . PHP_EOL;
}
// Prints:
// Hello <Nia>
// Name is required.
The helper cleans and validates the string. Escaping is kept at the HTML output boundary.
Task: Normalize a comma-separated tag list
Build a small helper that turns a comma-separated string into display-ready tags.
Requirements
- Use strict types.
- Accept one raw string such as
php, strings, validation. - Split on commas.
- Trim each part.
- Drop empty parts.
- Preserve the remaining tag text without HTML escaping inside the helper.
- Print the normalized tags joined with
|. - Include at least one input with extra spaces and repeated commas.
Show solution
<?php
declare(strict_types=1);
/**
* @return list<string>
*/
function normalizeTags(string $rawTags): array
{
$tags = [];
foreach (explode(',', $rawTags) as $tag) {
$tag = trim($tag);
if ($tag !== '') {
$tags[] = $tag;
}
}
return $tags;
}
$tags = normalizeTags(' php, strings,, validation, ');
echo implode(' | ', $tags) . PHP_EOL;
// Prints:
// php | strings | validation
The helper normalizes the internal value only. It does not HTML-escape the tags because escaping belongs at the output boundary where the target context is known.
Task: Validate and render a short profile summary
Create helpers for accepting a short profile summary and rendering it safely in HTML.
Requirements
- Use strict types.
- Trim the submitted summary before validation.
- Reject an empty summary after trimming.
- Reject summaries longer than 80 bytes using
strlen(). - Return the clean summary from one helper.
- Escape the clean summary for HTML in a separate rendering helper.
- Show one accepted summary containing HTML-sensitive characters.
- Show one rejected empty summary and one rejected overlong summary.
Show solution
<?php
declare(strict_types=1);
function cleanSummary(string $summary): string
{
$summary = trim($summary);
if ($summary === '') {
throw new InvalidArgumentException('Summary is required.');
}
if (strlen($summary) > 80) {
throw new InvalidArgumentException('Summary must be 80 bytes or fewer.');
}
return $summary;
}
function renderHtmlText(string $value): string
{
return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
$summary = cleanSummary(' Builder of <small> PHP tools ');
echo renderHtmlText($summary) . PHP_EOL;
foreach ([' ', str_repeat('x', 81)] as $candidate) {
try {
cleanSummary($candidate);
} catch (InvalidArgumentException $exception) {
echo $exception->getMessage() . PHP_EOL;
}
}
// Prints:
// Builder of <small> PHP tools
// Summary is required.
// Summary must be 80 bytes or fewer.
The validation helper returns a clean internal string. The rendering helper performs HTML escaping separately, so the application does not store an HTML-specific value.