we talked a little about regular expressions when we were discussing replacing text.
a regular expression is simply a string in a particular format used for pattern matching. in php, the syntax is /pattern/options.
before we talk about what pattern looks like, options allows us to change how the expression is matched. the options are case-sensitive.
| option | change |
|---|---|
i |
matches case-insensitive |
m |
allows matching the beginnings and ends of each line, not just the beginning and end of the whole string |
s |
treats new lines as whitespace instead of special non-whitespace characters |
x |
ignores most whitespace |
A |
only match the beginning |
D |
only match the end |
S |
inspect the pattern more thoroughly to try to save processor time when it's repeated |
U |
find the shortest possible matches (longest matches are the default) |
X |
return error on undefined characters starting with \ |
J |
allows duplicate names for sub-patterns (unwise in almost all situations) |
u |
force everything to function as utf-8 |
n |
turn off group capturing |
r |
don't match equivalent ascii and non-ascii characters |
i, s and u are the only three you'll likely use on a regular basis, often in combination. the order you add options doesn't matter. it applies them all at once regardless.
the first thing to know about patterns is that there are many special characters that can be found inside them.
| character | meaning |
|---|---|
. |
matches any character |
\d |
matches any digit |
\D |
matches anything but a digit |
\s |
matches any whitespace character |
\S |
matches anything but whitespace |
\w |
matches any letter or digit |
\W |
matches anything but a letter or digit |
\u |
matches the unicode character following \u in hex |
^ |
matches the beginning of a string (or line with m option) |
$ |
matches the end of a string (or line with m option) |
\b |
matches the beginning or end of a word (\bword or word\b) |
| |
or |
^ |
not |
\ |
special character follows |
if you're in unicode mode (option u), you have many more special characters available.
| character | meaning |
|---|---|
\p{L} |
matches any letter (including accented and extended characters) |
\p{M} |
matches any accent or diacritical mark |
\p{N} |
matches any number character (including non-indian numbers) |
\p{P} |
matches any punctuation |
\p{S} |
matches any symbol |
\p{Z} |
matches any separator |
\p{C} |
matches any control character |
\p{Lu} |
matches any capital letter |
\p{Ll} |
matches any lowercase letter |
\p{Lt} |
matches any title-case letter (this one can be a little unpredictable) |
\p{Lm} |
matches any modifier |
\p{Nd} |
matches any indian digit |
\p{Nl} |
matches any roman numeral |
\p{No} |
matches any fraction or numeric symbol |
\p(Sc} |
matches any currency symbol |
\p{Arabic} |
matches arabic characters |
\p{Cyrillic} |
matches cyrillic characters |
\p{Greek} |
matches greek characters |
\p{Han} |
matches chinese characters |
\p{Latin} |
matches latin characters |
defining the patterns also uses some special characters.
| character | meaning |
|---|---|
[abcde] |
matches anything in the brackets |
[^abcde] |
matches anything not in the brackets |
[a-e] |
matches any character between the two specified (letters or numbers) |
a* |
matches the character zero or more times |
a? |
matches the character zero or one time |
a+ |
matches the character one or more times |
a{x} |
matches the character or string x times |
a{x, y} |
matches the character or string between x and y times |
a{x,} |
matches the character or string at least x times |
(abcde) |
treats the contents as a group |
the simplest match test is whether a string does or doesn't match the regular expression. to do that, we use preg_match.
$isItIn = preg_match("/wiki/i", "the largest encyclopedia in the world is wikipedia."); // true
you can use preg_match_all to check how many times a string matches.
$howManyTimes = preg_match_all("/ped/i", "the largest encyclopedia in the world is wikipedia."); // 2
these two functions can do much more, though. they can return the matches.
preg_match_all("/\p{L}{5,}/iu", "the largest encyclopedia in the world is wikipedia.", $matches);
$matches[0] is now an array of all the matches. in this case, all groups of letters at least 5 long -- ["largest", "encyclopedia", "world", "wikipedia"].
in this case, $matches only has one element (0). for more complex patterns, more elements are used for groups within those matches.
matching email addresses using regular expressions is complex and rarely works well but here's a simple example to get the email addresses and just the username parts using preg_match_all from a string. it uses the information from some of our previous examples as its sample text. (this could be passed in as a variable but it's just text for simplicity here.)
preg_match_all("/(\S+)@(\S+)/iu", "jasmine park [email protected] is 174cm tall, weighs 51kg and has blue hair and brown eyes.\nkatherine law [email protected] is 157cm tall, weighs 56kg and has black hair and brown eyes.\nsusan yu [email protected] is 160.1cm tall, weighs 60.4kg and has red hair and gray eyes.\njen bull [email protected] is 165cm tall, weighs 58kg and has blonde hair and green eyes.\nlori bratt [email protected] is 170.3cm tall, weighs 67.2kg and has pink hair and blue eyes.", $matches);
$matches[0] is ["[email protected]", "[email protected]", "[email protected]", "[email protected]", "[email protected]"].
$matches[1] is ["jasmine.park", "klaw", "susany", "jennbull", "lb"].
$matches[2] is ["php.org", "php.org", "php.org", "php.org", "php.org"].
these correspond to the whole match then each () group within those matches. the order remains constant across all the arrays within $matches.
the same task can be accomplished on a collection of strings in an array using preg_grep. we can take an array of names and emails and use the same type of regular expression to return only the emails.
$mixedArray = ["jasmine park", "[email protected]", "katherine law", "[email protected]", "susan yu", "[email protected]", "jen bull", "[email protected]", "lori bratt", "[email protected]"];
$matches = preg_grep("/\S+@\S+/iu", $mixedArray);
$matches is ["[email protected]", "[email protected]", "[email protected]", "[email protected]", "[email protected]"]. note that the numeric keys for the items in the new array are the same as in the original array. the original array looks like this, if you include its keys...
$mixedArray = [0 => "jasmine park", 1 => "[email protected]", 2 => "katherine law", 3 => "[email protected]", 4 => "susan yu", 5 => "[email protected]", 6 => "jen bull", 7 => "[email protected]", 8 => "lori bratt", 9 => "[email protected]"]
so the resulting array looks like this...
$matches = [1 => "[email protected]", 3 => "[email protected]", 5 => "[email protected]", 7 => "[email protected]", 9 => "[email protected]"]
we can reset the numbering with array_values.
$matches = array_values($matches);
now $matches looks like this...
$matches = [0 => "[email protected]", 1 => "[email protected]", 2 => "[email protected]", 3 => "[email protected]", 4 => "[email protected]"]
preg_grep can also do the inverse. if you want everything from the original array except what matches, you can pass an extra parameter on the end of the function, PREG_GREP_INVERT.
$mixedArray = ["jasmine park", "[email protected]", "katherine law", "[email protected]", "susan yu", "[email protected]", "jen bull", "[email protected]", "lori bratt", "[email protected]"];
$matches = preg_grep("/\S+@\S+/iu", $mixedArray, PREG_GREP_INVERT);
now produces...
$matches = [0 => "jasmine park", 2 => "katherine law", 4 => "susan yu", 6 => "jen bull", 8 => "lori bratt"]
everything but the emails -- exactly the inverse of the results from the first time.
one of the most common simple uses for regular expressions is string replacement. instead of finding exact matches like with the str_replace function we have already used, preg_replace allows us to match using regular expressions.
$messyText = "sometimes people write \nthings with \tlots of spaces in the wrong places and \neven new lines\t in the \tmiddles of sentences like this.";
$cleanText = preg_replace("/\s+/", " ", $messyText);
now...
$cleanText = "sometimes people write things with lots of spaces in the wrong places and even new lines in the middles of sentences like this."
any whitespace -- including tabs, new lines and multiple spaces -- has been replaced with a single space. this would be an easy task to perform manually on such a short block of text but imagine doing it to an entire document or a whole book where these spacing errors are frequent. of course, you can be selective and fix multiple consecutive spaces without eliminating new lines if that's what your aim is.
a similar regular expression can be used to split messy text, too, as an example. preg_split functions much like explode, which we've already seen. but, instead of a single string used to split text, it allows us to use anything that matches the pattern.
$messyText = "sometimes people write \nthings with \tlots of spaces in the wrong places and \neven new lines\t in the \tmiddles of sentences like this.";
$splitText = preg_split("/[^\w]+/", $messyText);
instead of just splitting on any whitespace of any length like in our replacement example, we've used \w, any letter or number, and added ^ to specify the opposite. so anything that's not a letter or number, however long, functions as a break for our array. $splitText is now an array of only the words in order. note, because this string ends with a non-letter/number character, preg_split adds an empty string to the end of the array. in other words, the . functions as a delimiter and what comes after it is an empty string.