Adding Comments
preg_match("/^ (1[-\s.])? # optional '1-', '1.' or '1' ( \( )? # optional opening parenthesis \d{3} # the area code (?(2) \) ) # if there was opening parenthesis, close it [-\s.]? # followed by '-' or '.' or space \d{3} # first 3 digits [-\s.]? # followed by '-' or '.' or space \d{4} # last 4 digits $/x",$number);Let’s put it within a code segment.
$numbers = array( "123 555 6789", "1-(123)-555-6789",The trick is to use the ‘x’ modifier at the end of the regular expression. It causes the whitespaces in the pattern to be ignored, unless they are escaped (\s). This makes it easy to add comments. Comments start with ‘#’ and end at a newline.
Greedy vs. Ungreedy
Before I start explaining this concept, I would like to show an example first. Let’s say we are looking to find anchor tags in an html text:$html = 'Hello World!'; if (preg_match_all('/.*<\/a>/',$html,$matches)) { print_r($matches); }The result will be as expected:
/* output: Array ( [0] => Array ( [0] => World! ) ) */Let’s change the input and add a second anchor tag:
$html = 'Hello World!'; if (preg_match_all('/.*<\/a>/',$html,$matches)) { print_r($matches); } /* output: Array ( [0] => Array ( [0] => Hello [1] => World! ) ) */Again, it seems to be fine so far. But don’t let this trick you. The only reason it works is because the anchor tags are on separate lines, and by default PCRE matches patterns only one line at a time (more info on: ‘m’ modifier). If we encounter two anchor tags on the same line, it will no longer work as expected:
$html = 'Hello World!'; if (preg_match_all('/.*<\/a>/',$html,$matches)) { print_r($matches); } /* output: Array ( [0] => Array ( [0] => Hello World! ) ) */This time the pattern matches the first opening tag, and last opening tag, and everything in between as a single match, instead of making two separate matches. This is due to the default behavior being “greedy”.
“When greedy, the quantifiers (such as * or +) match as many character as possible.”If you add a question mark after the quantifier (.*?) it becomes “ungreedy”:
$html = 'Hello World!'; // note the ?'s after the *'s if (preg_match_all('/.*?<\/a>/',$html,$matches)) { print_r($matches); } /* output: Array ( [0] => Array ( [0] => Hello [1] => World! ) ) */Now the result is correct. Another way to trigger the ungreedy behavior is to use the U pattern modifier.
Lookahead and Lookbehind Assertions
A lookahead assertion searches for a pattern match that follows the current match. This might be explained easier through an example.The following pattern first matches for ‘foo’, and then it checks to see if it is followed by ‘bar’:
$pattern = '/foo(?=bar)/'; preg_match($pattern,'Hello foo'); // false preg_match($pattern,'Hello foobar'); // trueIt may not seem very useful, as we could have simply checked for ‘foobar’ instead. However, it is also possible to use lookaheads for making negative assertions. The following example matches ‘foo’, only if it is NOT followed by ‘bar’.
$pattern = '/foo(?!bar)/'; preg_match($pattern,'Hello foo'); // true preg_match($pattern,'Hello foobar'); // false preg_match($pattern,'Hello foobaz'); // trueLookbehind assertions work similarly, but they look for patterns before the current match. You may use (?< for positive assertions, and (?<! for negative assertions.
The following pattern matches if there is a ‘bar’ and it is not following ‘foo’.
$pattern = '/(?<!foo)bar/'; preg_match($pattern,'Hello bar'); // true preg_match($pattern,'Hello foobar'); // false preg_match($pattern,'Hello bazbar'); // true
Conditional (If-Then-Else) Patterns
Regular expressions provide the functionality for checking certain conditions. The format is as follows:(?(condition)true-pattern|false-pattern) or (?(condition)true-pattern)The condition can be a number. In which case it refers to a previously captured subpattern.
For example we can use this to check for opening and closing angle brackets:
$pattern = '/^(<)?[a-z]+(?(1)>)$/'; preg_match($pattern, ''); // true preg_match($pattern, ''); // false preg_match($pattern, 'hello'); // trueIn the example above, ’1′ refers to the subpattern (<), which is also optional since it is followed by a question mark. Only if that condition is true, it matches for a closing bracket.
The condition can also be an assertion:
// if it begins with 'q', it must begin with 'qu' // else it must begin with 'f' $pattern = '/^(?(?=q)qu|f)/'; preg_match($pattern, 'quake'); // true preg_match($pattern, 'qwerty'); // false preg_match($pattern, 'foo'); // true preg_match($pattern, 'bar'); // false
Filtering Patterns
There are various reasons for input filtering when developing web applications. We filter data before inserting it into a database, or outputting it to the browser. Similarly, it is necessary to filter any arbitrary string before including it in a regular expression. PHP provides a function named preg_quote to do the job.In the following example we use a string that contains a special character (*).
$word = '*world*'; $text = 'Hello *world*!'; preg_match('/'.$word.'/', $text); // causes a warning preg_match('/'.preg_quote($word).'/', $text); // trueSame thing can be accomplished also by enclosing the string between \Q and \E. Any special character after \Q is ignored until \E.
$word = '*world*'; $text = 'Hello *world*!'; preg_match('/\Q'.$word.'\E/', $text); // trueHowever, this second method is not 100% safe, as the string itself can contain \E.
Non-capturing Subpatterns
Subpatterns, enclosed by parentheses, get captured into an array so that we can use them later if needed. But there is a way to NOT capture them also.Let’s start with a very simple example:
preg_match('/(f.*)(b.*)/', 'Hello foobar', $matches); echo "f* => " . $matches[1]; // prints 'f* => foo' echo "b* => " . $matches[2]; // prints 'b* => bar'Now let’s make a small change by adding another subpattern (H.*) to the front:
preg_match('/(H.*) (f.*)(b.*)/', 'Hello foobar', $matches); echo "f* => " . $matches[1]; // prints 'f* => Hello' echo "b* => " . $matches[2]; // prints 'b* => foo'The $matches array was changed, which could cause the script to stop working properly, depending on what we do with those variables in the code. Now we have to find every occurence of the $matches array in the code, and adjust the index number accordingly.
If we are not really interested in the contents of the new subpattern we just added, we can make it ‘non-capturing’ like this:
preg_match('/(?:H.*) (f.*)(b.*)/', 'Hello foobar', $matches); echo "f* => " . $matches[1]; // prints 'f* => foo' echo "b* => " . $matches[2]; // prints 'b* => bar'By adding ‘?:’ at the beginning of the subpattern, we no longer capture it in the $matches array, so the other array values do not get shifted.
Named Subpatterns
There is another method for preventing pitfalls like in the previous example. We can actually give names to each subpattern, so that we can reference them later on using those names instead of array index numbers. This is the format: (?Ppattern)We could rewrite the first example in the previous section, like this:
preg_match('/(?Pf.*)(?Pb.*)/', 'Hello foobar', $matches); echo "f* => " . $matches['fstar']; // prints 'f* => foo' echo "b* => " . $matches['bstar']; // prints 'b* => bar'Now we can add another subpattern, without disturbing the existing matches in the $matches array:
preg_match('/(?PH.*) (?Pf.*)(?Pb.*)/', 'Hello foobar', $matches); echo "f* => " . $matches['fstar']; // prints 'f* => foo' echo "b* => " . $matches['bstar']; // prints 'b* => bar' echo "h* => " . $matches['hi']; // prints 'h* => Hello'
Comments
Post a Comment