Ah, dawno nic nie wrzucałem ;). Chciałbym się dziś podzielić dwoma poradami związanymi z regex'ami (wyrażeniami regularnymi).
Komentarze w wyrażeniach regularnych
Szybkie pytanie: co dopasowuje poniższe wyrażenie regularne?
preg_match('/.*(?=.{6,})(?=.*\d)(?=.*[a-zA-Z]).*/', $something);
Myślę, że nawet wprawionemu w bojach programiście przetworzenie tego wyrażenia zajmie krótką chwilę. W praktyce wyrażenia regularne szybko robią się skomplikowane, ekstremalnie trudne w czytaniu i jeszcze trudniejsze w debugowaniu. Można jednak te problemy rozwiązać za pomocą odrobiny formatowania i dodania komentarzy.
preg_match('/^
.* # Match any number of characters...
(?=.{6,}) # ... AND match at least 6 characters (lookahead) ...
(?=.*\d) # ... AND match one digit after any number of characters (lookahead) ...
(?=.*[a-zA-Z]) # ... AND match one letter after any number of characters (lookahead) ...
.* # ... AND allow any number of characters later.
$/x', $password);
Prawda, że czytelniej?
Modyfikator x
powoduje, że wszystkie niewyescape'owane białe znaki zostaną zignorowane, a wszystko od znaku #
do końca linii zostanie potraktowane jako komentarz i zignorowane.
O możliwości komentowania wyrażeń regularnych dowiedziałem się z artykułu Advanced Regular Expression Tips and Techniques. Zachęcam do jego przeczytania - jest tam więcej ciekawych porad, m.in. na temat callbacków czy nadawania nazw grupom.
Debugger do regexpów
Istnieje świetne narzędzie do analizy i debugowania regexpów - Debuggex. Wyświetla on strukturę wyrażenia graficznie, pozwalając na analizę krok po kroku.
Spójrzmy na przykład z poprzedniej części tego posta:
Prawdziwa potęga tego narzędzia objawia się przy bardzo złożonych wyrażeniach regularnych, jak na przykład:
^(?:(?:ht|f)tp(?:s?)\:\/\/|~\/|\/)?(?:\w+:\w+@)?(?:(?:[-\w]+\.)+(?:com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|museum|travel|[a-z]{2}))(?::[\d]{1,5})?(?:(?:(?:\/(?:[-\w~!$+|.,=]|%[a-f\d]{2})+)+|\/)+|\?|#)?(?:(?:\?(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)(?:&(?:[-\w~!$+|.,*:]|%[a-f\d{2}])+=?(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)*)*(?:#(?:[-\w~!$+|.,*:=]|%[a-f\d]{2})*)?$
Polecam skopiowanie powyższego wyrażenia do Debuggexa. Z jednej strony widać wyraźnie, jak ten regexp działa i do czego służy; z drugiej strony, sekcja Some random matches pokazuje, jak dziwne rzeczy przejdą przez nie poprawnie.
Uwaga o wydajności wyrażeń regularnych
Jak zapewne wielu programistów, którzy swą przygodę zaczęli od programowania gier, jestem sceptycznie nastawiony do wszelkich rzeczy pracujących na stringach. W szczególności, regexpy mogą na pierwszy rzut oka wydawać się wysoce niewydajne - ot skomplikowane dopasowanie tekstu zapisane w formie tekstu. Z pewnością napisanie tego w formie normalnego kodu - acz dłuższe - musi być wydajniejsze, prawda?
Otóż nie. Wyrażenia regularne są tak na prawdę programem - są (bardzo skondensowanym) opisem maszyny stanów, przez którą dopasowywany tekst stopniowo przechodzi, i jeśli uda się doprowadzić tę maszynę do stanu terminalnego, to dopasowanie jest uznawane za udane. Co to dokładnie znaczy? Polecam przyglądnąć się jeszcze raz rysunkom, które tworzy Debuggex - to dokładnie struktura tejże maszyny stanów. Taki system reprezentowany jest w programie w sposób bardzo wydajny; prawdopodobnie dużo wydajniejszy od naiwnego parsera, którego moglibyśmy napisać w niewielkiej liczbie linii kodu.
Co więcej, niektóre języki programowania (jak na przykład Common Lisp lub Java) pozwalają prekompilować wyrażenia regularne - jeżeli spodziewamy się, że dane wyrażenie regularne będzie używane wielokrotnie w czasie działania programu, to możemy zbudować maszynę stanów tylko raz (w przeciwieństwie do tworzenia jej na nowo za każdym razem, gdy chcemy dopasować jakiś napis), a następnie używać jej wszędzie tam, gdzie potrzebujemy korzystać z danego wyrażenia regularnego. Są nawet języki, które kompilują regexpy automatycznie.