danielstjules/sliceable-stringy
Stringy is a PHP string manipulation library with full multibyte support, offering a fluent API for camelCase, snake_case, slugs, truncation, and more. The sliceable variant adds JavaScript-style string slicing.
Install with Composer. The package is on Packagist as danielstjules/sliceable-stringy and pulls in danielstjules/stringy as its base class. Pin the version in composer.json so a future Packagist change can't surprise you:
composer require danielstjules/sliceable-stringy:^1.0
Since the last release was 2014, treat it as a vendored, unmaintained dependency: commit composer.lock and be prepared to fork if you need PHP 8 fixes.
Where to look first.
src/SliceableStringy.php — the whole feature lives here; it extends Stringy and adds slice parsing.tests/ — the fastest way to confirm exact syntax (brackets vs. bare start:stop, step support, negative-index behaviour). Read the tests before trusting the README.danielstjules/stringy README for the ~60 string methods you inherit for free (chars, upper, contains, etc.).First use case. Anywhere you currently hand-roll substr($s, $start, $stop - $start) with offset math — especially when the range itself is user-supplied (report column ranges, template expressions, log-slice tools, fixed-width file parsing). Python-style slicing turns that into a single, readable call:
use SliceableStringy\SliceableStringy;
$s = SliceableStringy::create('Hello World');
echo $s->slice('[0:5]'); // "Hello"
echo $s->slice('[-5:]'); // "World"
echo $s->slice('[::2]'); // "HloWrd"
Basic positive and negative ranges. The whole point is that start is inclusive, stop is exclusive, and negatives count from the end — exactly like Python.
$s = SliceableStringy::create('abcdefghij');
$s->slice('[2:4]'); // "cd"
$s->slice('[:3]'); // "abc"
$s->slice('[7:]'); // "hij"
$s->slice('[-3:]'); // "hij"
$s->slice('[-6:-3]'); // "efg"
Step slicing. A third segment steps through the string; a negative step reverses it.
$s->slice('[::2]'); // "acegi"
$s->slice('[1::3]'); // "beh"
$s->slice('[::-1]'); // "jihgfedcba"
Chain with inherited Stringy methods. Because SliceableStringy is a Stringy, you can slice and then continue with the rich API rather than dropping back to raw strings:
$initials = SliceableStringy::create($fullName)
->slice('[0:1]')
->upper();
Wrap it in an application service. Never let slice syntax leak into every call site. Put the parsing behind one small class so you can swap the implementation later (see Gotchas):
final class FieldRangeExtractor
{
public function extract(string $value, string $range): string
{
return SliceableStringy::create($value)->slice($range)->__toString();
}
}
Inject this adapter where you need it, and write a contract test against it with a data provider covering [0:1], [:-1], [::2], [100:], [-1:0], etc. That test suite becomes your safety net when you eventually migrate off this package.
Templating / user-facing syntax. If you're exposing ranges to users (e.g. a Twig filter or a saved report config), validate the expression before calling slice(), because out-of-range and malformed input handling is where the library is weakest:
if (!preg_match('/^\[-?\d*:-?\d*(?::-?\d+)?\]$/', $range)) {
throw new InvalidArgumentException("Invalid slice: {$range}");
}
Fixed-width parsing. Combine slicing with Stringy::chars() or repeated calls when parsing flat files:
$record = SliceableStringy::create($line);
$id = $record->slice('[0:8]');
$name = $record->slice('[8:38]');
symfony/string, and this package followed. Don't build new architecture on it without a migration plan; do use it for a short, well-tested utility class.??, and many deprecations in mb_* and Stringy. Test on your target PHP version before committing; if it breaks, fork and patch the one class you actually use.mb_substr (bytes/characters mismatch is a classic bug). Add a test with UTF-8 input like 'héllo' and with emoji before trusting it in user-facing code.[5:2] (empty?), [::0] (error?), [100:] (empty string?), and whitespace inside brackets. The parser may silently return '' on malformed input instead of throwing.'[1:3]') or optional ('1:3'), and whether the parser accepts spaces ('[1 : 3]'). Pick one form in your codebase and validate against it.slice() call runs a regex/parse over the expression. For hot loops (parsing thousands of lines), precompute integer offsets once and call substr/mb_substr directly, or cache the parsed slice object per expression.RangeSlicer interface now with a SliceableStringy implementation behind it. When you migrate to symfony/string (which has slice() and splice()), or a 10-line substr helper, you swap one class instead of hunting call sites.var_dump((string) $s) alongside strlen($s) — the length is usually the culprit (bytes vs. characters, or a trailing newline from the source file).src/ or a forks/ directory is legally fine — and is often the pragmatic answer for a 47-star, decade-old dependency.How can I help you explore Laravel packages today?