bermudaphp / stringy
A comprehensive PHP string manipulation library with full Unicode support
Requires
- php: ^8.0|^8.1
- neitanod/forceutf8: ^2.0
This package is auto-updated.
Last update: 2025-05-21 20:12:55 UTC
README
Read this documentation in Russian.
Overview
bermudaphp/stringy is a comprehensive PHP string manipulation library with full Unicode support. It provides both immutable and mutable string classes with a consistent API, making string operations more reliable and convenient compared to native PHP functions.
Table of Contents
Installation
composer require bermudaphp/stringy
Basic Concepts
The library provides two main string classes:
- Str: Immutable string class. Operations return new string instances without modifying the original.
- StrMutable: Mutable string class. Operations modify the string in place and return the same instance.
Both implement the StringInterface contract, providing a consistent API for string manipulation.
Class Examples
Str (Immutable)
The immutable string class returns a new instance for every operation that would change the string.
Creating Immutable Strings
use Bermuda\Stdlib\Str; // Create from string $str = Str::from('Hello World'); // Create with specific encoding $russianStr = Str::from('Привет мир', 'UTF-8'); // Create through constructor $str = new Str('Hello World'); // alternative $str = Stringy::of('Hello World');
Lazy Initialization with createLazy()
The createLazy()
static method allows you to create lazily initialized string objects that are only constructed when actually accessed. This can significantly improve performance by delaying expensive operations until they're truly needed.
// Create a lazy-initialized string object $lazy = \Bermuda\Stdlib\Str::createLazy(static function (\Bermuda\Stdlib\Str $str) { $str->__construct('construct call'); }); // No initialization occurs until the object is accessed echo $lazy->value; // Triggers initialization: "construct call"
How It Works
Unlike traditional objects that are initialized immediately, lazy ghost objects:
Create a minimal placeholder object initially.
Only execute your initializer function when a property or method is accessed.
Initialize the object just-in-time with the values you specify.
When to Use
Lazy initialization is particularly valuable when:
Resource-heavy initialization: Loading data from files, databases, or APIs.
Conditional usage: When objects may not be used in all code paths.
Performance optimization: Delaying expensive operations until absolutely necessary.
Memory management: Reducing memory usage by not initializing unused objects.
Advanced Example
// Creating multiple lazy string objects for a report $reportFields = [ 'title' => Str::createLazy(function($str) use ($reportId) { $data = $database->fetchReportTitle($reportId); $str->__construct($data); }), 'summary' => Str::createLazy(function($str) use ($reportId) { $data = $database->fetchReportSummary($reportId); $str->__construct($data); }), 'content' => Str::createLazy(function($str) use ($reportId) { // This potentially large content is only loaded if actually displayed $data = $database->fetchReportContent($reportId); $str->__construct($data); }) ]; // Only the title and summary are initialized - content remains lazy echo $reportFields['title']->toUpperCase(); echo $reportFields['summary']->truncate(100); // If this condition is false, content is never loaded from the database if ($showFullReport) { echo $reportFields['content']; }
Basic Properties
$str = Str::from('Hello'); // Get the underlying string value echo $str->value; // "Hello" // Get the current encoding echo $str->encoding; // "UTF-8" (default) // Check if string contains multibyte characters var_dump($str->isMultibyte); // bool(false) $russianStr = Str::from('Привет'); var_dump($russianStr->isMultibyte); // bool(true)
String Conversion and Basic Operations
$str = Str::from('Hello World'); // Convert to string echo $str->toString(); // "Hello World" echo $str; // "Hello World" (uses __toString()) // Create a copy $copy = $str->copy(); // Creates new Str instance with same value $copy === $str; // false // Encode to different character set $latin1 = $str->encode('ISO-8859-1'); // Get byte count (different from character count for multibyte strings) echo $str->getBytes(); // 11 // Get string length in characters echo $str->length(); // 11 echo count($str); // 11 (using Countable interface) // Check if string is empty var_dump($str->isEmpty()); // bool(false) var_dump(Str::from('')->isEmpty()); // bool(true) // Check if string is blank (empty or only whitespace) var_dump(Str::from(' ')->isBlank()); // bool(true)
Character Access
$str = Str::from('Hello World'); // Get character at position echo $str->charAt(0); // "H" echo $str->charAt(6); // "W" echo $str->charAt(-1); // "d" (negative indices count from end) // Get character as a new string object $firstChar = $str->at(0); // Returns Str('H') $lastChar = $str->at(-1); // Returns Str('d') // Get first and last characters $first = $str->first(); // Returns Str('H') $last = $str->last(); // Returns Str('d') // Check if position exists var_dump($str->has(5)); // bool(true) var_dump($str->has(20)); // bool(false) // Get index bounds echo $str->firstIndex(); // 0 echo $str->lastIndex(); // 10 // Array access (for reading only in Str) echo $str[0]; // "H" echo $str[-1]; // "d"
Substring Operations
$str = Str::from('Hello World'); // Extract substring echo $str->substring(0, 5); // "Hello" echo $str->substring(6); // "World" (to the end) echo $str->substring(-5); // "World" (from 5th last character to end) // Get start/end of string echo $str->start(5); // "Hello" echo $str->end(5); // "World" // Remove from start/end echo $str->removeStart(6); // "World" echo $str->removeEnd(6); // "Hello" // Get substring between delimiters echo $str->between('H', 'o'); // "ell" // Get substring before/after echo $str->before('World'); // "Hello " echo $str->after('Hello'); // " World" // Include delimiter in before/after echo $str->before('World', true); // "Hello W" echo $str->after('Hello', true); // "Hello World" // Split into two parts list($first, $second) = $str->split(' '); echo $first; // "Hello" echo $second; // "World" // Split by delimiter $parts = $str->explode(' '); // Returns array of Str objects $parts = $str->explode(' ', PHP_INT_MAX, true); // Returns array of strings
String Comparison
$str = Str::from('Hello World'); // Compare with another string var_dump($str->equals('Hello World')); // bool(true) var_dump($str->equals('hello world')); // bool(false) var_dump($str->equals('hello world', false)); // bool(true) - case insensitive // Compare with any of multiple strings var_dump($str->equalsAny(['Hello', 'World', 'Hello World'])); // bool(true) // Check if starts with var_dump($str->startsWith('Hello')); // bool(true) var_dump($str->startsWith(['Hi', 'Hello'])); // bool(true) var_dump($str->startsWith('hello', false)); // bool(true) - case insensitive // Check if ends with var_dump($str->endsWith('World')); // bool(true) var_dump($str->endsWith(['Earth', 'World'])); // bool(true) // Check if contains var_dump($str->contains('lo Wo')); // bool(true) var_dump($str->contains(['lo', 'Wo'])); // bool(true) // Check if contains all var_dump($str->containsAll(['Hello', 'World'])); // bool(true)
Search Operations
$str = Str::from('Hello World, Hello Earth'); // Find position of substring echo $str->indexOf('Hello'); // 0 echo $str->indexOf('Hello', 1); // 13 (starting from position 1) echo $str->indexOf('hello', 0, false); // 0 (case insensitive) var_dump($str->indexOf('Bye')); // NULL (not found) // Find last position of substring echo $str->lastIndexOf('Hello'); // 13 // Count occurrences echo $str->countSubstr('Hello'); // 2 echo $str->countSubstr('hello', false); // 2 (case insensitive)
Case Conversion
$str = Str::from('hello world'); // Convert to uppercase/lowercase echo $str->toUpperCase(); // "HELLO WORLD" echo $str->toLowerCase(); // "hello world" // Capitalize first character echo $str->capitalize(); // "Hello world" // Uncapitalize first character echo Str::from('Hello World')->uncapitalize(); // "hello World" // Capitalize each word echo $str->capitalizeWords(); // "Hello World" // Swap case of each character echo Str::from('Hello')->swapCase(); // "hELLO" // Title case (smarter capitalization) echo $str->titleize(); // "Hello World" echo $str->titleize(['of', 'the']); // "Hello World" (with ignored words)
Format Conversion
$str = Str::from('hello_world-example'); // Convert to different formats echo $str->toCamelCase(); // "helloWorldExample" echo $str->toPascalCase(); // "HelloWorldExample" echo $str->toSnakeCase(); // "hello_world_example" echo $str->toKebabCase(); // "hello-world-example" // Custom delimiter echo $str->delimit('.'); // "hello.world.example"
Whitespace Handling
$str = Str::from(' Hello World '); // Trim whitespace echo $str->trim(); // "Hello World" echo $str->trimStart(); // "Hello World " echo $str->trimEnd(); // " Hello World" // Custom characters to trim echo Str::from('__Hello__')->trim('_'); // "Hello" // Remove all whitespace echo $str->stripWhitespace(); // "HelloWorld" // Collapse multiple whitespace to single space echo Str::from('Hello World')->collapseWhitespace(); // "Hello World"
String Modification
$str = Str::from('Hello World'); // Insert at position echo $str->insert(' Dear', 5); // "Hello Dear World" // Pad string echo $str->pad('*', 15); // "**Hello World**" echo $str->padStart('-', 15); // "----Hello World" echo $str->padEnd('=', 15); // "Hello World====" // Wrap with character echo $str->wrap('"'); // "\"Hello World\"" // Check if wrapped var_dump(Str::from('"Hello"')->isWrapped('"')); // bool(true) // Add to start/end echo $str->prepend('Dear '); // "Dear Hello World" echo $str->append('!'); // "Hello World!" // Add prefix/suffix if not exists echo $str->ensurePrefix('Hello '); // "Hello World" (unchanged) echo $str->ensureSuffix('!'); // "Hello World!" // Remove prefix/suffix echo Str::from('HelloWorld')->removeSuffix('World'); // "Hello" echo Str::from('HelloWorld')->removePrefix('Hello'); // "World"
Search and Replace
$str = Str::from('Hello World'); // Replace text echo $str->replace('World', 'Earth'); // "Hello Earth" echo $str->replace(['Hello', 'World'], ['Hi', 'Earth']); // "Hi Earth" echo $str->replace('world', 'Earth', false); // "Hello Earth" (case insensitive) // Replace first/last occurrence echo Str::from('Hello Hello')->replaceFirst('Hello', 'Hi'); // "Hi Hello" echo Str::from('Hello Hello')->replaceLast('Hello', 'Hi'); // "Hello Hi" // Replace with pattern echo $str->replaceBy('/[aeiou]/i', '*'); // "H*ll* W*rld" // Replace with callback $result = $str->replaceCallback('/[A-Z]/u', function($match) { return '_' . strtolower($match[0]); }); // "_hello _world"
String Transformation
$str = Str::from('Hello World'); // Reverse echo $str->reverse(); // "dlroW olleH" // Shuffle characters echo $str->shuffle(); // Random order, e.g. "ldWroHl eol" // Repeat echo Str::from('Hi ')->repeat(3); // "Hi Hi Hi " // Truncate echo Str::from('This is a long sentence')->truncate(10); // "This is..." echo Str::from('This is a long sentence')->truncate(10, '---'); // "This is---" echo Str::from('This is a long sentence')->truncate(10, '...', true); // "This..." // Transform with callback echo $str->transform(function($s) { return strtoupper($s) . '!'; }); // "HELLO WORLD!" // Tabs and spaces echo Str::from("Hello\tWorld")->tabsToSpaces(2); // "Hello World" echo Str::from("Hello World")->spacesToTabs(2); // "Hello\tWorld" // Format with sprintf echo Str::from('Hello, %s!')->format('John'); // "Hello, John!"
Type Validation and Conversion
$str = Str::from('123'); // Check types var_dump($str->isNumeric()); // bool(true) var_dump($str->isAlpha()); // bool(false) var_dump($str->isAlphanumeric()); // bool(true) var_dump($str->isHex()); // bool(true) var_dump($str->isUpperCase()); // bool(false) var_dump($str->isLowerCase()); // bool(false) var_dump($str->hasUpperCase()); // bool(false) var_dump($str->hasLowerCase()); // bool(false) var_dump($str->hasDigits()); // bool(true) var_dump($str->hasSymbols()); // bool(false) // Convert to types echo $str->toNumber(); // int(123) var_dump(Str::from('true')->toBoolean()); // bool(true) var_dump(Str::from('true')->isBoolean()); // bool(true) // JSON operations var_dump(Str::from('{"a":1}')->isJson()); // bool(true) echo Str::from('Hello')->toJson(); // "\"Hello\"" // Other type checks var_dump(Str::from('a:1:{s:1:"a";i:1;}')->isSerialized()); // bool(true) var_dump(Str::from('SGVsbG8=')->isBase64()); // bool(true) var_dump(Str::from('2023-05-17')->isDate()); // bool(true) // Convert to date $date = Str::from('2023-05-17')->toDate();
String Segmentation
$str = Str::from("Hello\nWorld\nExample"); // Split into lines $lines = $str->lines(); // Array of Str objects, one per line // Split into words $words = Str::from('Hello World Example')->words(); // ["Hello", "World", "Example"] // Convert to array of characters $chars = $str->toArray(); // ["H", "e", "l", "l", "o", ... ]
Regular Expressions
$str = Str::from('Hello 123 World'); // Match pattern $found = $str->match('/\d+/', $matches); var_dump($found); // bool(true) echo $matches[0]; // "123" // Match all occurrences $found = $str->matchAll('/\w+/', $matches); var_dump($matches[0]); // Array with all words
Other Operations
$str = Str::from('Hello World'); // Hash the string echo $str->hash(); // SHA-256 hash echo $str->hash('md5'); // MD5 hash // ASCII conversion echo Str::from('Café')->toAscii(); // "Cafe" // Output $str->print(); // Outputs "Hello World" // Iterate through characters $str->each(function($char, $index) { echo "$index: $char\n"; return true; // Continue iteration });
Iterator Usage
$str = Str::from('Hello'); // Get iterator $iterator = $str->getIterator(); // Use in foreach foreach ($iterator as $index => $char) { echo "$index: $char\n"; }
StrMutable (Mutable)
The mutable string class modifies the string in place and returns the same instance for method chaining.
Creating Mutable Strings
use Bermuda\Stdlib\StrMutable; // Create from string $str = StrMutable::create('Hello World'); // Create with specific encoding $russianStr = StrMutable::create('Привет мир', 'UTF-8'); // Create through constructor $str = new StrMutable('Hello World'); // alternative $str = Stringy::mutable('Hello World'); // Set string value directly $str = StrMutable::create('Hello'); $str->setString('New Value');
Most methods in StrMutable have the same API as Str, but modify the string in place:
$str = StrMutable::create('Hello World'); // Chain operations $str->toUpperCase() ->trim() ->replace('WORLD', 'EARTH'); echo $str; // "HELLO EARTH" // Substring modifies in place $str->substring(0, 5); echo $str; // "HELLO"
Array Access (Mutable)
$str = StrMutable::create('Hello'); // Read echo $str[1]; // "e" // Write $str[0] = 'J'; echo $str; // "Jello" // Remove character unset($str[4]); echo $str; // "Jell"
StringIterator
The StringIterator class allows character-by-character iteration with various navigation methods.
Creating and Basic Usage
use Bermuda\Stdlib\StringIterator; // Create directly $iterator = new StringIterator('Hello'); // Create through string object $str = Str::from('World'); $iterator = $str->getIterator(); // Basic iteration while ($iterator->valid()) { echo $iterator->current(); $iterator->next(); } // Outputs: "World" // Get the string echo $iterator->__toString(); // "World" // Create new iterator with different string $newIterator = $iterator->withString('Hello');
Navigation
$iterator = new StringIterator('Hello World'); // Get current state echo $iterator->current(); // "H" (initial position is 0) echo $iterator->key(); // 0 (current position) // Move forward/backward $iterator->next(); echo $iterator->current(); // "e" $iterator->forward(2); // Move 2 steps forward echo $iterator->current(); // "l" $iterator->backward(); // Move 1 step back echo $iterator->current(); // "e" // Jump to position $iterator->moveTo(6); echo $iterator->current(); // "W" // Reset position $iterator->rewind(); echo $iterator->key(); // 0 // Check position var_dump($iterator->isStart()); // bool(true) var_dump($iterator->isEnd()); // bool(false) $iterator->moveTo($iterator->lastIndex()); var_dump($iterator->isEnd()); // bool(false) $iterator->next(); var_dump($iterator->isEnd()); // bool(true) var_dump($iterator->valid()); // bool(false)
Reading
$iterator = new StringIterator('Hello World'); // Read next characters $iterator->moveTo(6); echo $iterator->readNext(5); // "World" // Read to the end (when length is null) $iterator->moveTo(6); echo $iterator->readNext(); // "World"
Stringy (Static Utility)
The Stringy class provides static utility methods for string manipulation.
use Bermuda\Stdlib\Stringy; // Convert string format echo Stringy::delimit('HelloWorld', '-'); // "hello-world" echo Stringy::delimit('hello_world-example', '.'); // "hello.world.example" // Trim whitespace echo Stringy::trim(' Hello '); // "Hello" echo Stringy::trimStart(' Hello '); // "Hello " echo Stringy::trimEnd(' Hello '); // " Hello" // With custom characters echo Stringy::trim('__Hello__', '_'); // "Hello" // Multibyte support echo Stringy::trim(' Привет '); // "Привет"
ClsHelper (Class Name Utility)
The ClsHelper class provides utilities for working with class names and namespaces.
use Bermuda\Stdlib\ClsHelper; $className = 'Bermuda\\Stringy\\StrMutable'; // Get namespace part echo ClsHelper::namespace($className); // "Bermuda\\Stringy" // Get basename (class without namespace) echo ClsHelper::basename($className); // "StrMutable" // Split into namespace and class name $parts = ClsHelper::split($className); // Returns: [0 => 'Bermuda\\Stringy', 1 => 'StrMutable'] // Validate class names var_dump(ClsHelper::isValidName('MyClass')); // bool(true) var_dump(ClsHelper::isValidName('Vendor\\MyClass')); // bool(true) var_dump(ClsHelper::isValidName('Vendor\\MyClass', false)); // bool(false) - no namespace allowed var_dump(ClsHelper::isValidName('0InvalidClass')); // bool(false)
Working with Multibyte Strings
All methods in Bermuda\Stringy properly handle multibyte strings, ensuring correct character handling for non-Latin alphabets and special characters:
Basic Multibyte Operations
// Create with specific encoding $russian = Str::from('Привет, мир!', 'UTF-8'); $chinese = Str::from('你好,世界!', 'UTF-8'); $arabic = Str::from('مرحبا بالعالم!', 'UTF-8'); // Character count vs byte count echo $russian->length(); // 12 (character count) echo $russian->getBytes(); // More than 12 (byte count) // Character access echo $russian->charAt(0); // "П" echo $chinese->charAt(0); // "你" // Substring extraction echo $russian->substring(0, 6); // "Привет" echo $chinese->substring(0, 2); // "你好" // Case conversion (where applicable) echo $russian->toUpperCase(); // "ПРИВЕТ, МИР!" echo $russian->toLowerCase(); // "привет, мир!"
Text Transformation
// Reverse (correctly handles multibyte) echo $russian->reverse(); // "!рим ,тевирП" // Replace echo $russian->replace('мир', 'свет'); // "Привет, свет!" // Trim echo Str::from(' Привет ')->trim(); // "Привет" // Character iterations $iterator = $russian->getIterator(); foreach ($iterator as $char) { echo $char . ' '; // "П р и в е т , м и р !" }
Array Access with Multibyte
$mutable = StrMutable::create('Привет'); echo $mutable[0]; // "П" $mutable[0] = 'К'; echo $mutable; // "Кривет" unset($mutable[5]); echo $mutable; // "Криве"
Working with Different Scripts
// Japanese $japanese = Str::from('こんにちは世界', 'UTF-8'); echo $japanese->length(); // 7 characters echo $japanese->substring(0, 5); // "こんにちは" // Thai (with combining diacritical marks) $thai = Str::from('สวัสดีโลก', 'UTF-8'); echo $thai->length(); // Correctly counts Thai characters with marks // Emoji $emoji = Str::from('Hello 👋 World 🌍', 'UTF-8'); echo $emoji->length(); // Correctly counts emoji characters echo $emoji->substring(6, 1); // "👋" // Mixed script text $mixed = Str::from('Hello Привет 你好 مرحبا', 'UTF-8'); foreach ($mixed->words() as $word) { echo $word . "\n"; // Correctly splits words across scripts }
License
This library is licensed under the MIT License. See the LICENSE file for details.
Contributing
Contributions are welcome! Please create a pull request on the GitHub repository.