The String Component

The String Component

The String component provides a single object-oriented API to work with three “unit systems” of strings: bytes, code points and grapheme clusters.

New in version 5.0: The String component was introduced in Symfony 5.0.

Installation

  1. $ composer require symfony/string

Note

If you install this component outside of a Symfony application, you must require the vendor/autoload.php file in your code to enable the class autoloading mechanism provided by Composer. Read this article for more details.

What is a String?

You can skip this section if you already know what a “code point” or a “grapheme cluster” are in the context of handling strings. Otherwise, read this section to learn about the terminology used by this component.

Languages like English require a very limited set of characters and symbols to display any content. Each string is a series of characters (letters or symbols) and they can be encoded even with the most limited standards (e.g. ASCII).

However, other languages require thousands of symbols to display their contents. They need complex encoding standards such as Unicode and concepts like “character” no longer make sense. Instead, you have to deal with these terms:

  • Code points: they are the atomic unit of information. A string is a series of code points. Each code point is a number whose meaning is given by the Unicode standard. For example, the English letter A is the U+0041 code point and the Japanese kana is the U+306E code point.
  • Grapheme clusters: they are a sequence of one or more code points which are displayed as a single graphical unit. For example, the Spanish letter ñ is a grapheme cluster that contains two code points: U+006E = n (“latin small letter N”) + U+0303 = ◌̃ (“combining tilde”).
  • Bytes: they are the actual information stored for the string contents. Each code point can require one or more bytes of storage depending on the standard being used (UTF-8, UTF-16, etc.).

The following image displays the bytes, code points and grapheme clusters for the same word written in English (hello) and Hindi (नमस्ते):

../_images/bytes-points-graphemes.png

Usage

Create a new object of type Symfony\Component\String\ByteString, Symfony\Component\String\CodePointString or Symfony\Component\String\UnicodeString, pass the string contents as their arguments and then use the object-oriented API to work with those strings:

  1. use Symfony\Component\String\UnicodeString;
  2. $text = (new UnicodeString('This is a déjà-vu situation.'))
  3. ->trimEnd('.')
  4. ->replace('déjà-vu', 'jamais-vu')
  5. ->append('!');
  6. // $text = 'This is a jamais-vu situation!'
  7. $content = new UnicodeString('नमस्ते दुनिया');
  8. if ($content->ignoreCase()->startsWith('नमस्ते')) {
  9. // ...
  10. }

Method Reference

Methods to Create String Objects

First, you can create objects prepared to store strings as bytes, code points and grapheme clusters with the following classes:

  1. use Symfony\Component\String\ByteString;
  2. use Symfony\Component\String\CodePointString;
  3. use Symfony\Component\String\UnicodeString;
  4. $foo = new ByteString('hello');
  5. $bar = new CodePointString('hello');
  6. // UnicodeString is the most commonly used class
  7. $baz = new UnicodeString('hello');

Use the `wrap() static method to instantiate more than one string object:

  1. $contents = ByteString::wrap(['hello', 'world']); // $contents = ByteString[]
  2. $contents = UnicodeString::wrap(['I', '❤️', 'Symfony']); // $contents = UnicodeString[]
  3. // use the unwrap method to make the inverse conversion
  4. $contents = UnicodeString::unwrap([
  5. new UnicodeString('hello'), new UnicodeString('world'),
  6. ]); // $contents = ['hello', 'world']

If you work with lots of String objects, consider using the shortcut functions to make your code more concise:

  1. // the b() function creates byte strings
  2. use function Symfony\Component\String\b;
  3. // both lines are equivalent
  4. $foo = new ByteString('hello');
  5. $foo = b('hello');
  6. // the u() function creates Unicode strings
  7. use function Symfony\Component\String\u;
  8. // both lines are equivalent
  9. $foo = new UnicodeString('hello');
  10. $foo = u('hello');
  11. // the s() function creates a byte string or Unicode string
  12. // depending on the given contents
  13. use function Symfony\Component\String\s;
  14. // creates a ByteString object
  15. $foo = s("\xfe\xff");
  16. // creates a UnicodeString object
  17. $foo = s('अनुच्छेद');

New in version 5.1: The `s() function was introduced in Symfony 5.1.

There are also some specialized constructors:

  1. // ByteString can create a random string of the given length
  2. $foo = ByteString::fromRandom(12);
  3. // by default, random strings use A-Za-z0-9 characters; you can restrict
  4. // the characters to use with the second optional argument
  5. $foo = ByteString::fromRandom(6, 'AEIOU0123456789');
  6. $foo = ByteString::fromRandom(10, 'qwertyuiop');
  7. // CodePointString and UnicodeString can create a string from code points
  8. $foo = UnicodeString::fromCodePoints(0x928, 0x92E, 0x938, 0x94D, 0x924, 0x947);
  9. // equivalent to: $foo = new UnicodeString('नमस्ते');

New in version 5.1: The second argument of `ByteString::fromRandom() was introduced in Symfony 5.1.

Methods to Transform String Objects

Each string object can be transformed into the other two types of objects:

  1. $foo = ByteString::fromRandom(12)->toCodePointString();
  2. $foo = (new CodePointString('hello'))->toUnicodeString();
  3. $foo = UnicodeString::fromCodePoints(0x68, 0x65, 0x6C, 0x6C, 0x6F)->toByteString();
  4. // the optional $toEncoding argument defines the encoding of the target string
  5. $foo = (new CodePointString('hello'))->toByteString('Windows-1252');
  6. // the optional $fromEncoding argument defines the encoding of the original string
  7. $foo = (new ByteString('さよなら'))->toCodePointString('ISO-2022-JP');

If the conversion is not possible for any reason, you’ll get an Symfony\Component\String\Exception\InvalidArgumentException.

There is also a method to get the bytes stored at some position:

  1. // ('नमस्ते' bytes = [224, 164, 168, 224, 164, 174, 224, 164, 184,
  2. // 224, 165, 141, 224, 164, 164, 224, 165, 135])
  3. b('नमस्ते')->bytesAt(0); // [224]
  4. u('नमस्ते')->bytesAt(0); // [224, 164, 168]
  5. b('नमस्ते')->bytesAt(1); // [164]
  6. u('नमस्ते')->bytesAt(1); // [224, 164, 174]
  1. // returns the number of graphemes, code points or bytes of the given string
  2. $word = 'नमस्ते';
  3. (new ByteString($word))->length(); // 18 (bytes)
  4. (new CodePointString($word))->length(); // 6 (code points)
  5. (new UnicodeString($word))->length(); // 4 (graphemes)
  6. // some symbols require double the width of others to represent them when using
  7. // a monospaced font (e.g. in a console). This method returns the total width
  8. // needed to represent the entire word
  9. $word = 'नमस्ते';
  10. (new ByteString($word))->width(); // 18
  11. (new CodePointString($word))->width(); // 4
  12. (new UnicodeString($word))->width(); // 4
  13. // if the text contains multiple lines, it returns the max width of all lines
  14. $text = "<<<END
  15. This is a
  16. multiline text
  17. END";
  18. u($text)->width(); // 14
  19. // only returns TRUE if the string is exactly an empty string (not even white spaces)
  20. u('hello world')->isEmpty(); // false
  21. u(' ')->isEmpty(); // false
  22. u('')->isEmpty(); // true
  23. // removes all white spaces from the start and end of the string and replaces two
  24. // or more consecutive white spaces inside contents by a single white space
  25. u(" \n\n hello world \n \n")->collapseWhitespace(); // 'hello world'

Methods to Change Case

  1. // changes all graphemes/code points to lower case
  2. u('FOO Bar')->lower(); // 'foo bar'
  3. // when dealing with different languages, uppercase/lowercase is not enough
  4. // there are three cases (lower, upper, title), some characters have no case,
  5. // case is context-sensitive and locale-sensitive, etc.
  6. // this method returns a string that you can use in case-insensitive comparisons
  7. u('FOO Bar')->folded(); // 'foo bar'
  8. u('Die O\'Brian Straße')->folded(); // "die o'brian strasse"
  9. // changes all graphemes/code points to upper case
  10. u('foo BAR')->upper(); // 'FOO BAR'
  11. // changes all graphemes/code points to "title case"
  12. u('foo bar')->title(); // 'Foo bar'
  13. u('foo bar')->title(true); // 'Foo Bar'
  14. // changes all graphemes/code points to camelCase
  15. u('Foo: Bar-baz.')->camel(); // 'fooBarBaz'
  16. // changes all graphemes/code points to snake_case
  17. u('Foo: Bar-baz.')->snake(); // 'foo_bar_baz'
  18. // other cases can be achieved by chaining methods. E.g. PascalCase:
  19. u('Foo: Bar-baz.')->camel()->title(); // 'FooBarBaz'

The methods of all string classes are case-sensitive by default. You can perform case-insensitive operations with the `ignoreCase() method:

  1. u('abc')->indexOf('B'); // null
  2. u('abc')->ignoreCase()->indexOf('B'); // 1

Methods to Append and Prepend

  1. // adds the given content (one or more strings) at the beginning/end of the string
  2. u('world')->prepend('hello'); // 'helloworld'
  3. u('world')->prepend('hello', ' '); // 'hello world'
  4. u('hello')->append('world'); // 'helloworld'
  5. u('hello')->append(' ', 'world'); // 'hello world'
  6. // adds the given content at the beginning of the string (or removes it) to
  7. // make sure that the content starts exactly with that content
  8. u('Name')->ensureStart('get'); // 'getName'
  9. u('getName')->ensureStart('get'); // 'getName'
  10. u('getgetName')->ensureStart('get'); // 'getName'
  11. // this method is similar, but works on the end of the content instead of on the beginning
  12. u('User')->ensureEnd('Controller'); // 'UserController'
  13. u('UserController')->ensureEnd('Controller'); // 'UserController'
  14. u('UserControllerController')->ensureEnd('Controller'); // 'UserController'
  15. // returns the contents found before/after the first occurrence of the given string
  16. u('hello world')->before('world'); // 'hello '
  17. u('hello world')->before('o'); // 'hell'
  18. u('hello world')->before('o', true); // 'hello'
  19. u('hello world')->after('hello'); // ' world'
  20. u('hello world')->after('o'); // ' world'
  21. u('hello world')->after('o', true); // 'o world'
  22. // returns the contents found before/after the last occurrence of the given string
  23. u('hello world')->beforeLast('o'); // 'hello w'
  24. u('hello world')->beforeLast('o', true); // 'hello wo'
  25. u('hello world')->afterLast('o'); // 'rld'
  26. u('hello world')->afterLast('o', true); // 'orld'

Methods to Pad and Trim

  1. // makes a string as long as the first argument by adding the given
  2. // string at the beginning, end or both sides of the string
  3. u(' Lorem Ipsum ')->padBoth(20, '-'); // '--- Lorem Ipsum ----'
  4. u(' Lorem Ipsum')->padStart(20, '-'); // '-------- Lorem Ipsum'
  5. u('Lorem Ipsum ')->padEnd(20, '-'); // 'Lorem Ipsum --------'
  6. // repeats the given string the number of times passed as argument
  7. u('_.')->repeat(10); // '_._._._._._._._._._.'
  8. // removes the given characters (by default, white spaces) from the string
  9. u(' Lorem Ipsum ')->trim(); // 'Lorem Ipsum'
  10. u('Lorem Ipsum ')->trim('m'); // 'Lorem Ipsum '
  11. u('Lorem Ipsum')->trim('m'); // 'Lorem Ipsu'
  12. u(' Lorem Ipsum ')->trimStart(); // 'Lorem Ipsum '
  13. u(' Lorem Ipsum ')->trimEnd(); // ' Lorem Ipsum'

Methods to Search and Replace

  1. // checks if the string starts/ends with the given string
  2. u('https://symfony.com')->startsWith('https'); // true
  3. u('report-1234.pdf')->endsWith('.pdf'); // true
  4. // checks if the string contents are exactly the same as the given contents
  5. u('foo')->equalsTo('foo'); // true
  6. // checks if the string content match the given regular expression
  7. u('avatar-73647.png')->match('/avatar-(\d+)\.png/');
  8. // result = ['avatar-73647.png', '73647']
  9. // checks if the string contains any of the other given strings
  10. u('aeiou')->containsAny('a'); // true
  11. u('aeiou')->containsAny(['ab', 'efg']); // false
  12. u('aeiou')->containsAny(['eio', 'foo', 'z']); // true
  13. // finds the position of the first occurrence of the given string
  14. // (the second argument is the position where the search starts and negative
  15. // values have the same meaning as in PHP functions)
  16. u('abcdeabcde')->indexOf('c'); // 2
  17. u('abcdeabcde')->indexOf('c', 2); // 2
  18. u('abcdeabcde')->indexOf('c', -4); // 7
  19. u('abcdeabcde')->indexOf('eab'); // 4
  20. u('abcdeabcde')->indexOf('k'); // null
  21. // finds the position of the last occurrence of the given string
  22. // (the second argument is the position where the search starts and negative
  23. // values have the same meaning as in PHP functions)
  24. u('abcdeabcde')->indexOfLast('c'); // 7
  25. u('abcdeabcde')->indexOfLast('c', 2); // 7
  26. u('abcdeabcde')->indexOfLast('c', -4); // 2
  27. u('abcdeabcde')->indexOfLast('eab'); // 4
  28. u('abcdeabcde')->indexOfLast('k'); // null
  29. // replaces all occurrences of the given string
  30. u('http://symfony.com')->replace('http://', 'https://'); // 'https://symfony.com'
  31. // replaces all occurrences of the given regular expression
  32. u('(+1) 206-555-0100')->replaceMatches('/[^A-Za-z0-9]++/', ''); // '12065550100'
  33. // you can pass a callable as the second argument to perform advanced replacements
  34. u('123')->replaceMatches('/\d/', function ($match) {
  35. return '['.$match[0].']';
  36. }); // result = '[1][2][3]'

New in version 5.1: The `containsAny() method was introduced in Symfony 5.1.

Methods to Join, Split, Truncate and Reverse

  1. // uses the string as the "glue" to merge all the given strings
  2. u(', ')->join(['foo', 'bar']); // 'foo, bar'
  3. // breaks the string into pieces using the given delimiter
  4. u('template_name.html.twig')->split('.'); // ['template_name', 'html', 'twig']
  5. // you can set the maximum number of pieces as the second argument
  6. u('template_name.html.twig')->split('.', 2); // ['template_name', 'html.twig']
  7. // returns a substring which starts at the first argument and has the length of the
  8. // second optional argument (negative values have the same meaning as in PHP functions)
  9. u('Symfony is great')->slice(0, 7); // 'Symfony'
  10. u('Symfony is great')->slice(0, -6); // 'Symfony is'
  11. u('Symfony is great')->slice(11); // 'great'
  12. u('Symfony is great')->slice(-5); // 'great'
  13. // reduces the string to the length given as argument (if it's longer)
  14. u('Lorem Ipsum')->truncate(3); // 'Lor'
  15. u('Lorem Ipsum')->truncate(80); // 'Lorem Ipsum'
  16. // the second argument is the character(s) added when a string is cut
  17. // (the total length includes the length of this character(s))
  18. u('Lorem Ipsum')->truncate(8, '…'); // 'Lorem I…'
  19. // if the third argument is false, the last word before the cut is kept
  20. // even if that generates a string longer than the desired length
  21. u('Lorem Ipsum')->truncate(8, '…', false); // 'Lorem Ipsum'

New in version 5.1: The third argument of `truncate() was introduced in Symfony 5.1.

  1. // breaks the string into lines of the given length
  2. u('Lorem Ipsum')->wordwrap(4); // 'Lorem\nIpsum'
  3. // by default it breaks by white space; pass TRUE to break unconditionally
  4. u('Lorem Ipsum')->wordwrap(4, "\n", true); // 'Lore\nm\nIpsu\nm'
  5. // replaces a portion of the string with the given contents:
  6. // the second argument is the position where the replacement starts;
  7. // the third argument is the number of graphemes/code points removed from the string
  8. u('0123456789')->splice('xxx'); // 'xxx'
  9. u('0123456789')->splice('xxx', 0, 2); // 'xxx23456789'
  10. u('0123456789')->splice('xxx', 0, 6); // 'xxx6789'
  11. u('0123456789')->splice('xxx', 6); // '012345xxx'
  12. // breaks the string into pieces of the length given as argument
  13. u('0123456789')->chunk(3); // ['012', '345', '678', '9']
  14. // reverses the order of the string contents
  15. u('foo bar')->reverse(); // 'rab oof'
  16. u('さよなら')->reverse(); // 'らなよさ'

New in version 5.1: The `reverse() method was introduced in Symfony 5.1.

Methods Added by ByteString

These methods are only available for ByteString objects:

  1. // returns TRUE if the string contents are valid UTF-8 contents
  2. b('Lorem Ipsum')->isUtf8(); // true
  3. b("\xc3\x28")->isUtf8(); // false

Methods Added by CodePointString and UnicodeString

These methods are only available for CodePointString and UnicodeString objects:

  1. // transliterates any string into the latin alphabet defined by the ASCII encoding
  2. // (don't use this method to build a slugger because this component already provides
  3. // a slugger, as explained later in this article)
  4. u('नमस्ते')->ascii(); // 'namaste'
  5. u('さよなら')->ascii(); // 'sayonara'
  6. u('спасибо')->ascii(); // 'spasibo'
  7. // returns an array with the code point or points stored at the given position
  8. // (code points of 'नमस्ते' graphemes = [2344, 2350, 2360, 2340]
  9. u('नमस्ते')->codePointsAt(0); // [2344]
  10. u('नमस्ते')->codePointsAt(2); // [2360]

Unicode equivalence is the specification by the Unicode standard that different sequences of code points represent the same character. For example, the Swedish letter å can be a single code point (U+00E5 = “latin small letter A with ring above”) or a sequence of two code points (U+0061 = “latin small letter A” + U+030A = “combining ring above”). The `normalize() method allows to pick the normalization mode:

  1. // these encode the letter as a single code point: U+00E5
  2. u('å')->normalize(UnicodeString::NFC);
  3. u('å')->normalize(UnicodeString::NFKC);
  4. // these encode the letter as two code points: U+0061 + U+030A
  5. u('å')->normalize(UnicodeString::NFD);
  6. u('å')->normalize(UnicodeString::NFKD);

Slugger

In some contexts, such as URLs and file/directory names, it’s not safe to use any Unicode character. A slugger transforms a given string into another string that only includes safe ASCII characters:

  1. use Symfony\Component\String\Slugger\AsciiSlugger;
  2. $slugger = new AsciiSlugger();
  3. $slug = $slugger->slug('Wôrķšƥáçè ~~sèťtïñğš~~');
  4. // $slug = 'Workspace-settings'
  5. // you can also pass an array with additional character substitutions
  6. $slugger = new AsciiSlugger('en', ['en' => ['%' => 'percent', '€' => 'euro']]);
  7. $slug = $slugger->slug('10% or 5€');
  8. // $slug = '10-percent-or-5-euro'
  9. // if there is no symbols map for your locale (e.g. 'en_GB') then the parent locale's symbols map
  10. // will be used instead (i.e. 'en')
  11. $slugger = new AsciiSlugger('en_GB', ['en' => ['%' => 'percent', '€' => 'euro']]);
  12. $slug = $slugger->slug('10% or 5€');
  13. // $slug = '10-percent-or-5-euro'
  14. // for more dynamic substitutions, pass a PHP closure instead of an array
  15. $slugger = new AsciiSlugger('en', function ($string, $locale) {
  16. return str_replace('❤️', 'love', $string);
  17. });

New in version 5.1: The feature to define additional substitutions was introduced in Symfony 5.1.

New in version 5.2: The feature to use a PHP closure to define substitutions was introduced in Symfony 5.2.

New in version 5.3: The feature to fallback to the parent locale’s symbols map was introduced in Symfony 5.3.

The separator between words is a dash (-) by default, but you can define another separator as the second argument:

  1. $slug = $slugger->slug('Wôrķšƥáçè ~~sèťtïñğš~~', '/');
  2. // $slug = 'Workspace/settings'

The slugger transliterates the original string into the Latin script before applying the other transformations. The locale of the original string is detected automatically, but you can define it explicitly:

  1. // this tells the slugger to transliterate from Korean language
  2. $slugger = new AsciiSlugger('ko');
  3. // you can override the locale as the third optional parameter of slug()
  4. $slug = $slugger->slug('...', '-', 'fa');

In a Symfony application, you don’t need to create the slugger yourself. Thanks to service autowiring, you can inject a slugger by type-hinting a service constructor argument with the Symfony\Component\String\Slugger\SluggerInterface. The locale of the injected slugger is the same as the request locale:

  1. use Symfony\Component\String\Slugger\SluggerInterface;
  2. class MyService
  3. {
  4. private $slugger;
  5. public function __construct(SluggerInterface $slugger)
  6. {
  7. $this->slugger = $slugger;
  8. }
  9. public function someMethod()
  10. {
  11. $slug = $this->slugger->slug('...');
  12. }
  13. }

Inflector

New in version 5.1: The inflector feature was introduced in Symfony 5.1.

In some scenarios such as code generation and code introspection, you need to convert words from/to singular/plural. For example, to know the property associated with an adder method, you must convert from plural (addStories() method) to singular ($story` property).

Most human languages have simple pluralization rules, but at the same time they define lots of exceptions. For example, the general rule in English is to add an s at the end of the word (book -> books) but there are lots of exceptions even for common words (woman -> women, life -> lives, news -> news, radius -> radii, etc.)

This component provides an Symfony\Component\String\Inflector\EnglishInflector class to convert English words from/to singular/plural with confidence:

  1. use Symfony\Component\String\Inflector\EnglishInflector;
  2. $inflector = new EnglishInflector();
  3. $result = $inflector->singularize('teeth'); // ['tooth']
  4. $result = $inflector->singularize('radii'); // ['radius']
  5. $result = $inflector->singularize('leaves'); // ['leaf', 'leave', 'leaff']
  6. $result = $inflector->pluralize('bacterium'); // ['bacteria']
  7. $result = $inflector->pluralize('news'); // ['news']
  8. $result = $inflector->pluralize('person'); // ['persons', 'people']

The value returned by both methods is always an array because sometimes it’s not possible to determine a unique singular/plural form for the given word.

This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.