Strings #

PHP has over 100 built-in functions for manipulating strings — from very simple ones like strlen() to complex ones like similar_text() and levenshtein(). You don’t need to memorize all of them, but there’s a core set that appears in almost all everyday PHP code. This article covers the most important string functions in an organized way — not just a list, but with context on when and how to use them, including the mb_ functions for multibyte strings (UTF-8, emoji, Asian characters) that you must use when dealing with non-ASCII text.

Basic String Information #

<?php
$text = "  Hello, PHP World!  ";

// String length — in BYTES, not characters
echo strlen($text);          // 20

// Length in characters (correct for UTF-8)
echo mb_strlen($text);       // 20 (same if all ASCII)
echo mb_strlen("日本語");     // 3 characters (but 9 bytes!)
echo strlen("日本語");        // 9 bytes

// Check whether the string is empty
var_dump($text === '');       // false
var_dump(empty($text));       // false
var_dump(empty(''));          // true
var_dump(empty('0'));         // true — gotcha!

Searching and Checking #

<?php
$text = "The quick brown fox jumps over the lazy dog";

// str_contains — PHP 8.0+, check for substring existence
var_dump(str_contains($text, 'fox'));    // true
var_dump(str_contains($text, 'cat'));    // false
var_dump(str_contains($text, ''));       // true — an empty string is always present

// str_starts_with and str_ends_with — PHP 8.0+
var_dump(str_starts_with($text, 'The'));   // true
var_dump(str_starts_with($text, 'the'));   // false — case-sensitive!
var_dump(str_ends_with($text, 'dog'));     // true
var_dump(str_ends_with($text, 'Dog'));     // false

// strpos — position of the first occurrence (0-based), false if absent
$position = strpos($text, 'fox');
var_dump($position);  // int(16)

// ANTI-PATTERN: loose comparison with false
if (strpos($text, 'fox') == false) { } // wrong! position 0 == false is also true!

// CORRECT: use === false
if (strpos($text, 'fox') === false) {
    echo "Not found\n";
}

// Or even better, use str_contains (PHP 8.0+)
if (!str_contains($text, 'fox')) {
    echo "Not found\n";
}

// strrpos — position of the LAST occurrence
$url  = 'https://example.com/path/to/file.php';
$pos  = strrpos($url, '/');
$file = substr($url, $pos + 1); // "file.php"

// substr_count — count substring occurrences
echo substr_count("banana", "an"); // 2
echo substr_count("hello world", "l"); // 3

// Case-insensitive search
$pos = stripos("Hello WORLD", "world"); // 6
var_dump(str_contains(strtolower($text), 'fox')); // portable case-insensitive way

Searching and Replacing #

<?php
// str_replace — replace all occurrences
$result = str_replace('fox', 'cat', "The fox and the fox");
// "The cat and the cat"

// Replace many at once — array to array
$clean = str_replace(
    ['<script>', '</script>', 'javascript:', 'onclick'],
    ['', '', '', ''],
    $userInput
);

// str_ireplace — case-insensitive
$result = str_ireplace('HELLO', 'Hi', "HELLO world Hello"); // "Hi world Hi"

// substr_replace — replace at a specific position
$text   = "Hello World";
$result = substr_replace($text, 'PHP', 6, 5); // "Hello PHP"
// replace 5 characters starting at position 6

// preg_replace — replace with regex
$clean = preg_replace('/\s+/', ' ', "there are   extra   spaces");
// "there are extra spaces"

$slug = preg_replace('/[^a-z0-9]+/', '-', strtolower("Hello PHP World!"));
// "hello-php-world-"
$slug = trim($slug, '-'); // "hello-php-world"

// str_repeat — repeat a string
echo str_repeat('=', 40);    // ========================================
echo str_repeat('ab', 3);    // ababab

// strtr — replace characters one by one (useful for transliteration)
$from = 'áàâäãåéèêëíìîïóòôöõúùûüý';
$to   = 'aaaaaaeeeeeiiiiooooouuuuy';
$clean = strtr($text, $from, $to);

// Or with an array mapping
$result = strtr("Hello World", ['Hello' => 'Hi', 'World' => 'Earth']);
// "Hi Earth"

Splitting and Joining #

<?php
// explode — split a string into an array
$csv = "apple,mango,orange,grape";
$fruits = explode(',', $csv);
// ['apple', 'mango', 'orange', 'grape']

// With a limit — at most N elements, the rest is joined in the last element
$limited = explode(',', $csv, 2);
// ['apple', 'mango,orange,grape']

// Negative limit — remove the last N elements
$withoutLast = explode(',', $csv, -1);
// ['apple', 'mango', 'orange']

// implode / join — join an array into a string
$text = implode(', ', $fruits);        // "apple, mango, orange, grape"
$text = implode(' | ', $fruits);       // "apple | mango | orange | grape"
$text = implode('', ['a', 'b', 'c']);  // "abc"

// str_split — split a string into an array per N characters
$chars  = str_split("Hello");       // ['H', 'e', 'l', 'l', 'o']
$chunks = str_split("Hello World", 3); // ['Hel', 'lo ', 'Wor', 'ld']

// chunk_split — split with a separator (useful for base64)
$base64  = base64_encode(file_get_contents('image.jpg'));
$wrapped = chunk_split($base64, 76, "\n"); // wrap every 76 characters

// wordwrap — wrap long text
$paragraph = "This is a very long text that needs to be wrapped at a certain width.";
$wrapped = wordwrap($paragraph, 40, "\n", true);

Case Manipulation #

<?php
$text = "hello world php programming";

// Basic case conversion
echo strtoupper($text);  // HELLO WORLD PHP PROGRAMMING
echo strtolower("HELLO"); // hello

// Capitalization
echo ucfirst($text);     // Hello world php programming
echo lcfirst("HELLO");   // hELLO
echo ucwords($text);     // Hello World Php Programming

// For multibyte (non-ASCII characters)
echo mb_strtoupper("héllo wörld", 'UTF-8'); // HÉLLO WÖRLD
echo mb_strtolower("HÉLLO WÖRLD", 'UTF-8'); // héllo wörld
echo mb_convert_case("héllo wörld", MB_CASE_TITLE, 'UTF-8'); // Héllo Wörld

// camelCase → snake_case
function camelToSnake(string $text): string
{
    return strtolower(preg_replace('/(?<!^)(?=[A-Z])/', '_', $text));
}
echo camelToSnake('camelCaseString');  // camel_case_string
echo camelToSnake('getUserById');      // get_user_by_id

// snake_case → camelCase
function snakeToCamel(string $text): string
{
    return lcfirst(str_replace('_', '', ucwords($text, '_')));
}
echo snakeToCamel('snake_case_string'); // snakeCaseString
echo snakeToCamel('get_user_by_id');    // getUserById

Trimming and Padding #

<?php
$text = "   Hello World   ";

// trim — remove whitespace from both sides
echo trim($text);         // "Hello World"
echo ltrim($text);        // "Hello World   " (left only)
echo rtrim($text);        // "   Hello World" (right only)

// trim with custom characters
echo trim("***Hello***", "*");   // "Hello"
echo trim("/path/to/dir/", "/"); // "path/to/dir"

// str_pad — padding for alignment
echo str_pad("42",   5, "0", STR_PAD_LEFT);    // "00042"
echo str_pad("Hello", 10, "-", STR_PAD_BOTH);  // "---Hello---"
echo str_pad("PHP",  10, " ", STR_PAD_RIGHT);  // "PHP       "

// A tidy table with str_pad
$items = [
    ['name' => 'Laptop',  'price' => 15000000],
    ['name' => 'Mouse',   'price' => 250000],
    ['name' => 'Monitor', 'price' => 5000000],
];

foreach ($items as $item) {
    echo str_pad($item['name'],  10) . str_pad(number_format($item['price']), 12, ' ', STR_PAD_LEFT) . "\n";
}
// Laptop      15,000,000
// Mouse          250,000
// Monitor      5,000,000

Substrings and Extraction #

<?php
$text = "Hello, World!";

// substr — take part of a string
echo substr($text, 7);        // "World!" (from position 7)
echo substr($text, 7, 5);     // "World" (5 characters from position 7)
echo substr($text, -6);       // "World!" (6 from the end)
echo substr($text, -6, 5);    // "World" (5 characters, 6 from the end)

// mb_substr — for multibyte
echo mb_substr("日本語テスト", 2, 3); // "語テス" (3 characters starting at position 2)

// strstr — take from where the substring is found
$email = "[email protected]";
echo strstr($email, '@');       // "@example.com" (including the delimiter)
echo strstr($email, '@', true); // "budi" (before the delimiter — before=true)

// strrchr — from the LAST occurrence
$path = "/var/www/html/index.php";
echo strrchr($path, '/');        // "/index.php"
echo ltrim(strrchr($path, '/'), '/'); // "index.php"

// basename() is better for paths:
echo basename($path);            // "index.php"
echo basename($path, '.php');    // "index"
echo dirname($path);             // "/var/www/html"
echo pathinfo($path, PATHINFO_EXTENSION); // "php"

Formatting and Output #

<?php
// sprintf — format a string
$name  = 'Budi';
$score = 95.67;
$rank  = 3;

echo sprintf("Name: %-15s Score: %06.2f Rank: %d", $name, $score, $rank);
// "Name: Budi            Score: 095.67 Rank: 3"

// Frequently used format specifiers:
// %s   — string
// %d   — decimal integer
// %f   — float (default 6 decimals)
// %.2f — float with 2 decimals
// %05d — 5-digit integer with leading zeros
// %-10s — left-aligned 10-char string
// %10s — right-aligned 10-char string

// number_format — format numbers with thousands separators
echo number_format(1234567.891);          // "1,234,568"
echo number_format(1234567.891, 2);       // "1,234,567.89"
echo number_format(1234567.891, 0, ',', '.'); // "1.234.568" (Indonesian format)
echo "Rp " . number_format(15000000, 0, ',', '.'); // "Rp 15.000.000"

// printf — like sprintf but prints directly
printf("Total: Rp %s\n", number_format(15000000, 0, ',', '.'));

// vsprintf / vprintf — from an argument array
$args  = ['Budi', 28, 'Jakarta'];
$text  = vsprintf("Name: %s, Age: %d, City: %s", $args);

// money_format is deprecated — use NumberFormatter
$fmt   = new NumberFormatter('id_ID', NumberFormatter::CURRENCY);
echo $fmt->format(15000000);       // "Rp15.000.000,00"
echo $fmt->formatCurrency(15000000, 'IDR'); // "Rp15.000.000,00"

Hashing and Encryption #

<?php
// One-way hashes
echo md5("password");           // 5f4dcc3b5aa765d61d8327deb882cf99 — DON'T use for passwords!
echo sha1("password");          // 5baa61e4c9b93f3f0682250b6cf8331b57ff68 — DON'T!
echo hash('sha256', "data");    // SHA-256 hash
echo hash('sha512', "data");    // SHA-512 hash

// The correct hash for passwords
$hash = password_hash('secret123', PASSWORD_BCRYPT);
var_dump(password_verify('secret123', $hash)); // true
var_dump(password_verify('wrong',    $hash)); // false

// Check whether the hash needs rehashing (if the algorithm changes)
if (password_needs_rehash($hash, PASSWORD_BCRYPT)) {
    $hash = password_hash('secret123', PASSWORD_BCRYPT); // rehash
}

// HMAC — hash with a secret key (for integrity verification)
$secret  = 'application-secret-key';
$payload = json_encode(['user_id' => 42, 'exp' => time() + 3600]);
$hmac    = hash_hmac('sha256', $payload, $secret);

// Verify the HMAC — use hash_equals for a timing-safe comparison
$valid = hash_equals($hmac, hash_hmac('sha256', $payload, $secret));

// Two-way encryption (symmetric)
$key   = sodium_crypto_secretbox_keygen();
$nonce = random_bytes(SODIUM_CRYPTO_SECRETBOX_NONCEBYTES);

$encrypted = sodium_crypto_secretbox('secret message', $nonce, $key);
$decrypted = sodium_crypto_secretbox_open($encrypted, $nonce, $key);
echo $decrypted; // "secret message"

// Encode/decode
echo base64_encode("binary data or text");
echo base64_decode("ZGF0YSBiaW5lciBhdGF1IHRla3M=");

// URL encoding
echo urlencode("price=Rp 15.000 & name=Budi");
// "price%3DRp+15.000+%26+name%3DBudi"

echo rawurlencode("price=Rp 15.000 & name=Budi");
// "price%3DRp%2015.000%20%26%20name%3DBudi" (RFC 3986 compliant)

$decoded = urldecode("price%3DRp+15.000");

Multibyte Functions (mb_) #

For all string operations involving non-ASCII characters (UTF-8, Unicode), always use the mb_ functions rather than the regular string functions:

<?php
$text = "Héllo Wörld 日本語 😀";

// mb_ equivalents
echo mb_strlen($text, 'UTF-8');              // 18 characters
echo mb_strtoupper($text, 'UTF-8');          // HÉLLO WÖRLD 日本語 😀
echo mb_substr($text, 6, 5, 'UTF-8');        // Wörld
echo mb_strpos($text, 'Wörld', 0, 'UTF-8'); // 6

// Set the default encoding for all mb_ functions
mb_internal_encoding('UTF-8');
mb_regex_encoding('UTF-8');

// After setting the internal encoding, you can omit the encoding argument:
echo mb_strlen($text);    // 18
echo mb_strtolower($text); // héllo wörld 日本語 😀

// Detect encoding
$encoding = mb_detect_encoding($text, ['UTF-8', 'ISO-8859-1', 'Windows-1252']);

// Convert encoding
$utf8   = mb_convert_encoding($latin1String, 'UTF-8', 'ISO-8859-1');
$latin1 = mb_convert_encoding($utf8String, 'ISO-8859-1', 'UTF-8');

// mb_convert_variables — convert entire variables
mb_convert_variables('UTF-8', 'ISO-8859-1', $array); // converts all array elements

// Important mb_ functions:
// mb_strlen     — length in characters
// mb_substr     — substring
// mb_strpos     — substring position
// mb_strrpos    — last position
// mb_strtolower — lowercase
// mb_strtoupper — uppercase
// mb_convert_case — case conversion with modes
// mb_str_split   — split into an array of characters (PHP 7.4+)
$chars = mb_str_split("日本語");  // ['日', '本', '語']

Comparison and Similarity Functions #

<?php
// strcmp — binary-safe comparison, returns 0 if equal
$cmp = strcmp("apple", "banana"); // negative (apple < banana)
$cmp = strcmp("banana", "apple"); // positive
$cmp = strcmp("apple", "apple");  // 0

// strcasecmp — case-insensitive
var_dump(strcasecmp("HELLO", "hello")); // int(0)

// strncmp — compare the first N characters
var_dump(strncmp("hello world", "hello PHP", 5)); // int(0) — first 5 characters match

// similar_text — similarity percentage of two strings
similar_text("Hello", "World", $percent);
echo round($percent, 1) . "%"; // 40%

similar_text("Budi Santoso", "Budi Hartono", $percent);
echo round($percent, 1) . "%"; // 75%

// levenshtein — minimum number of edit operations
echo levenshtein("kitten", "sitting"); // 3
echo levenshtein("laptp",  "laptop");  // 1 — one insertion

// soundex and metaphone — phonetic matching
echo soundex("Robert");   // R163
echo soundex("Rupert");   // R163 — same!
echo metaphone("Smith");  // SM0
echo metaphone("Smythe"); // SM0 — same!

Summary #

  • PHP 8.0+: str_contains, str_starts_with, str_ends_with replace the verbose strpos() !== false pattern that’s error-prone when === isn’t used.
  • strpos() returns false or an integer — always use === false to check for absence. Position 0 is valid and equals false when compared with ==.
  • mb_ functions for non-ASCII textstrlen("日本語") returns 9 (bytes), not 3 (characters). Always use mb_strlen, mb_substr, mb_strtolower, etc. for multibyte content.
  • password_hash + password_verify for passwords — never use md5 or sha1 for password hashing. Use PASSWORD_BCRYPT or PASSWORD_ARGON2ID.
  • hash_equals for timing-safe hash comparisons — prevents timing attacks that could expose tokens through execution time differences.
  • sprintf for complex formatting — cleaner than repeated concatenation when you have many variables with specific formats.
  • number_format($number, 0, ',', '.') for Rupiah formatting — 0 decimals, comma as the decimal separator, dot as the thousands separator.
  • mb_internal_encoding('UTF-8') in your application bootstrap ensures all mb_ functions use UTF-8 by default without mentioning the encoding on every call.

← Previous: Articles & Resources   Next: IO →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact