Wyrażenia regularne są łańcuchami znaków, które pomagają w odnajdywaniu w tekście różnych wzorców. Są nieocenioną pomocą w przypadku, gdy trzeba np. sprawdzić poprawność składniową adresu e-mail czy też wyodrębnić z tekstu ciąg wielkich liter o zadanej długości. Praktyczne zastosowania wyrażeń regularnych można mnożyć w nieskończoność – wszystko sprowadza się do porównania łańcucha znaków z zadanym wzorcem i ewentualnym zamienianiu niektórych fragmentów tego wzorca na inne znaki.
Ten tutorial podzielę na dwie części. W pierwszej omówię same wyrażenia regularne, zaś część drugą poświęcę na ich zastosowanie w php. Ograniczę się wyłącznie do PERL-owych wyrażeń, które są alternatywą dla mniej wydajnych i odchodzących z użycia wyrażeń POSIX-owych (część elementów jest wspólna dla jednych i drugich).
Aby jednak nie zaciemniać za bardzo przykładów w moim tutorialu, przedstawię w tym momencie najprostszą funkcję do obsługi wyrażeń regularnych w php – preg_match(). Jej najprostsza składnia przedstawia się następująco:
1 |
Funkcja ta posiada dodatkowe parametry, o których wspomnę w dalszej części tutoriala. Należy również zaznaczyć, że wyrażenia używane w tej funkcji muszą zaczynać się i kończyć jednym dowolnym (oprócz backslasha) znakiem niealfanumerycznym, który musi być następnie unikany backslashem wewnątrz wyrażenia. Przyjęło się, że jest to slash, jednak w przypadku wyrażenia z dużą ilością slashów wewnątrz (np. adresy http) można zastosować inny, np. #.
Podstawy
Tak naprawdę każdy tekst nie zawierający znaków specjalnych jest wyrażeniem regularnym. Zwyczajne
1 | Ala ma kota |
jest wyrażeniem regularnym, do którego pasuje tekst Słyszałem, że Ala ma kota.. Warto zaznaczyć, że wyrażenia regularne są wrażliwe na wielkość liter. Oczywiście stosowanie samych takich ciągów jako wyrażeń mija się z celem. Skomplikujmy nieco nasze wyrażenie.
1 | Ala ma (kota|psa) |
Tutaj nawias służy nam do zgrupowania wyrazów, które mogą pojawić się w tym miejscu. Wyrazy oddzielone są pionową kreską. W związku z tym powyższe wyrażenie pasuje zarówno do Ala ma kota jak i Ala ma psa. Gdybyśmy potrzebowali w którymś z wyrazów użyć nawiasu lub pionowej kreski, powinniśmy uniknąć ich przetworzenia za pomocą backslasha. Oto lista wszystkich znaków specjalnych, które muszą zostać poprzedzone backslashem, gdy mają być traktowane jako zwykły tekst.
1 | ^ $ < > ( ) [ { . * ? + | \ |
Oczywiście nie byłoby potrzeby unikania powyższych znaków, gdyby każdy z nich nie służył specjalnemu celowi. I tak na przykład, znak ^ służy do dopasowania wyrażenia do początku łańcucha, natomiast $ do jego końca.
1 2 3 4 | $str = 'moje ulubione danie to placki'; preg_match('/placki/', $str); // zwróci true; preg_match('/placki$/', $str); // zwróci true; preg_match('/^placki/' $str); // zwróci false; |
Oczywiście gdy nasze wyrażenie będzie brzmiało “^placki$”, jedynym spełniającym go tekstem będzie słowo placki. Takie zastosowanie wyrażeń nie ma jednak sensu – dużo wydajniejsze jest użycie zwykłego porównania.
Jeśli chcemy sprawdzić, czy w danym miejscu w tekście występuje jeden z kilku możliwych znaków, używamy do tego nawiasów kwadratowych. W nawiasach tych możemy zastosować zakres – np. a-e, 0-9, A-Z. Nie ma potrzeby unikania znaku -, ponieważ zakres jest sprawdzany wyłącznie tam, gdzie to ma sens, np. między dwiema małymi, dwiema wielkimi literami lub między dwiema cyframi. Istnieje również możliwość zaprzeczenia podanemu zakresowi, stawiając znak ^ zaraz po lewym nawiasie kwadratowym. Przykłady:
1 2 3 | preg_match('/[aeiou]/', 'fasola'); // zwróci true - w łańcuchu znajdują się te samogłoski; preg_match('/[A-Z]/', 'fasola'); // zwróci false - w łańcuchu nie ma żadnych wielkich liter; preg_match('/[^0-9.-]/', '100 zł'); // zwróci true - w łańcuchu znajdują się znaki spoza podanego zakresu |
Czasem lepszym rozwiązaniem od użycia kilku zakresów naraz jest zastosowanie znaków specjalnych, reprezentujących szczególne grupy znaków. Oto najczęściej używane znaki specjalne:
| Znak | Odpowiednik | Opis |
| . | (brak) | Dowolny znak |
| \s | [\t\r\n ] | Białe spacje |
| \S | [^\t\r\n ] | Wszystko oprócz białych spacji |
| \d | [0-9] | Cyfry |
| \D | [^0-9] | Wszystko oprócz cyfr |
| \w | [a-zA-Z0-9_] | Znaki stanowiące słowa |
| \W | [^a-zA-Z0-9_] | Domyśl się |
Kwantyfikatory
Aby móc sprawdzić, czy litera B znajduje się w wyrażeniu pięć (lub pięćset pięć) razy, nie ma sensu jej tam aż tyle pisać. Wystarczy użyć odpowiedniego kwantyfikatora. Używa się ich również do sprawdzenia, czy dany znak występuje raz albo w ogóle, co najmniej zero razy, co najmniej raz czy też od tylu do tylu razy. A oto one:
| Wyrażenie | Pasujące łańcuchy | Wyjaśnienie |
| ^a?$ | ”, ‘a’ | Zero lub jeden raz |
| ^a*$ | ”, ‘a’, ‘aa’, ‘aaa’… | Co najmniej zero razy |
| ^a+$ | ‘a’, ‘aa’, ‘aaa’… | Co najmniej raz |
| ^a{7}$ | ‘aaaaaaa’ | Dokładnie 7 razy |
| ^a{4,7}$ | ‘aaaa’, ‘aaaaa’, ‘aaaaaa’, ‘aaaaaaa’ | Od 4 do 7 razy |
| ^a{4,}$ | ‘aaaa’, ‘aaaaa’, ‘aaaaaa’… | Co najmniej cztery razy |
| ^a{,4}$ | ”, ‘a’, ‘aa’, ‘aaa’, ‘aaaa’ | Co najwyżej cztery razy |
Oczywiście kwantyfikatory działają również dla całych grup znaków, zarówno tych pogrupowanych za pomocą nawiasów kwadratowych jak i tych z oznaczeniami z poprzedniej tabeli. Przykładowo, \w+ oznacza co najmniej jeden znak będący częścią słowa, natomiast [0-5]{2} oznacza dwie sąsiadujące ze sobą cyfry z zakresu od 0 do 5.
Przykłady
Przebrnąłeś już przez trochę suchej teorii – czas na sensowniejsze przykłady. Pamiętasz Alę z początku tutoriala? Gdyby między słowami znajdowała się więcej niż jedna spacja, to mimo iż treść jest wciąż taka sama, dla skryptu jest diametralnie różna. Nauczmy więc parsera uznawać więcej niż jedną spację między słowami, tworząc takie wyrażenie:
1 2 3 4 5 6 7 8 9 | $expr = '/Ala\s+ma\s+kota/'; preg_match($expr, 'Ala ma kota'); // zwróci true preg_match($expr, ' Ala ma kota'); // też zwróci true $str = 'Ala ma kota'; // puste linie są częścią łańcucha znaków! preg_match($expr, $str); // też zwróci true preg_match($expr, 'Ala nie ma kota'); // zwróci false |
A co, gdybyśmy chcieli, by wyrażenie było prawdziwe zarówno gdy ma kota, jak i go nie ma? Zmodyfikujmy nasze wyrażenie następująco:
1 2 3 4 | $expr = '/Ala\s+(nie\s+)?ma\s+kota/'; preg_match($expr, 'Ala ma kota'); // true preg_match($expr, 'Ala nie ma kota'); // true preg_match($expr, 'ala NIE ma kota!!!11'); // false - wielkość liter ma znaczenie |
Zostawmy Alę już na dobre i zajmijmy się czymś, co prędzej zastosujemy na naszych stronach. Załóżmy, że chcemy, aby użytkownicy przysyłali nam obrazy – obrazy te mają jednak mieć nazwy w formacie obraz_xxx (gdzie xxx to jakaś liczba) i mogą mieć rozszerzenie jpg, gif lub png. Wyrażenie, które nam pomoże w sprawdzeniu nazwy, wyglądać będzie następująco:
1 | /^obraz_\d+\.(jpg|gif|png)$/ |
Krótka analiza:
Przede wszystkim nazwa pliku musi zaczynać się od słowa obraz – umieszczamy więc znak ^ i piszemy po nim obraz. Następnie po znaku podkreślenia znajduje się dowolna ilość cyfr, o czym mówi nam znak \d z kwantyfikatorem + (co najmniej jedna). Następnie w nazwie pliku musi znajdować się kropka – w wyrażeniu musimy uniknąć ją backslashem, w przeciwnym wypadku kropka zastąpiłaby nam dowolny znak. Następnie stosujemy alternatywę trzech rozszerzeń, po których nasze wyrażenie musi się kończyć, o czym informuje znak dolara.
Inny przykład – mamy profil użytkownika, w którym może on wpisać adres swojej strony www. Stajemy przed dwoma problemami: czy użytkownik wpisał http:// na początku swojego adresu? (jeśli nie, należy to dokleić przy generowaniu linka do jego strony) czy użytkownik wpisał poprawny adres www? (zakładamy, że dopuszczamy jedynie “typowe adresy”, jak np. www.onet.pl, a nie www.strona.pl/cos.php?a=b%3B…itd.)
Pierwsze wyrażenie jest bardzo proste i nie robi nic więcej oprócz odpowiedzi na pierwsze pytanie:
1 | preg_match('#^http://#', $adres_strony); |
Drugie wyrażenie będzie bardziej skomplikowane. Uwzględnijmy w nim, że wewnątrz adresu nie mogą znajdować się inne znaki niż alfanumeryczne, kropka, myślnik czy znak podkreślenia oraz przypilnujmy, by domena najwyższego poziomu nie była zbyt długa. Adres może, ale nie musi zaczynać się od http://.
1 | #^(http://)?([a-zA-Z0-9][a-zA-Z0-9_-]*[a-zA-Z0-9]\.)+[a-z]{2,4}/?$# |
Straszne? Wiem. Przeanalizujmy to wyrażenie. Na początku sprawdzamy, czy http:// występuje najwyżej raz za pomocą kwantyfikatora ?. Słowo http:// musimy wziąć w nawias, w przeciwnym wypadku znak zapytania stosowałby się wyłącznie do pojedynczego slasha. Następnie mamy kolejny nawias – grupę kolejnych subdomen, z których każda kończy się kropką. Kropkę w nawiasie koniecznie unikamy, jeśli nie chcemy, aby została przetworzona jako dowolny znak. Na początku i na końcu subdomeny mogą znajdować się wyłącznie litery i cyfry, a w środku dowolna ilość znaków ze środkowego nawiasu kwadratowego. Uwaga: zakładamy, że kolejne subdomeny mają przynajmniej dwa znaki długości. Po ostatniej kropce mogą wystąpić od dwóch do czterech małych liter jako domena najwyższego poziomu. Opcjonalnie na końcu adresu może znajdować się slash.
W tej części to już tyle. Następna pojawi się za tydzień i omówię w niej najistotniejsze funkcje PHP obsługujące wyrażenia regularne, a także bardziej zaawansowane konstrukcje w samych wyrażeniach. Żeby wam się specjalnie nie nudziło, mam dla was cztery ćwiczenia. Odpowiedzi możecie zostawiać tutaj w komentarzach lub w odpowiednim dziale na forum.
A oto ćwiczenia:
- Numer telefonu w postaci xxx-xxx-xxx, z opcjonalnym numerem kierunkowym kraju na początku (np. 123-456-789, +48 666 666 666)
- Adres filmiku na youtube – zwrócić uwagę na składnię linku do filmiku, dopuszczalne znaki w jego identyfikatorze i ich ilość. Zakładamy, że chodzi o najprostszy link, bez dodatkowych parametrów typu feature=related w adresie.
- Adres e-mail. Chyba każdemu się przyda. Nie googlać, będę umiał rozpoznać wygooglane
- Zmodyfikować przykład z adresem www tak, aby dopuszczał również jednoliterowe (lub jednocyfrowe) subdomeny.




















