Statyczna analiza kodu cz 4 – PHP Mess Detector cz 2

W tym poście opiszę w jaki sposób można tworzyć własne reguły i zestawy reguł dla PHPMD. Jeśli chciałbyś się dowiedzieć czym jest PHPMD lub uzupełnić wiedzę na ten temat to zapraszam do lektury poprzedniego postu opisującego od podstaw PHPMD. Zachęcam także do zapoznania się z tym i z tym postem – oba poświęcone są narzędziom do statycznej analizy kodu.

I jeszcze jedna uwaga – przykłady znajdujące się w tym poście są zaczerpnięte bezpośrednio z dokumentacji PHPMD.

A teraz o tworzeniu własnych reguł

W pierwszej kolejności należy utworzyć plik z klasą, która będzie implementować interface \PHPMD\Rule. Znacznie lepszym rozwiązaniem będzie jednak zamiast implementować ten interface rozszerzyć klasę \PHPMD\AbstractRule. Zawiera ona całą logikę niezbędną do utworzenia klasy z regułą.

Kolejnym krokiem będzie stworzenie wewnątrz klasy metody apply, która jako argument przyjmuje obiekt będący instancją \PHPMD\AbstractNode. Ten obiekt reprezentuje klasę, metodę, funkcję lub interface, który możemy poddać testom. Pytanie jednak skąd wiadomo, że ten obiekt zawiera klasę a nie np. metodę? Aby to określić należy implementować jeden z czterech specjalnych interfaceów (\PHPMD\Rule\ClassAware, \PHPMD\Rule\FunctionAware, \PHPMD\Rule\InterfaceAware, \PHPMD\Rule\MethodAware). Ich jedynym zadaniem jest oznaczenie jakiego typu będzie przekazywany element testowanego kodu w parametrze do metody apply. Przykład klasy poniżej:

Teraz pozostaje tylko wywołać metodę addViolation, która dodaje informacje o błędzie do raportu końcowego w odpowiednim miejscu.

A teraz o regułach bazujących na danych z PHP Depend

PHPMD może posłużyć także do analizy istniejących już danych zwróconych PHP Depend i na ich bazie określić czy z kodem jest coś nie tak. Zasada tworzenia takiej reguły jest bardzo podobna do tych analizujących kod.

Pierwsze z czym należy się zapoznać to z listą metryk jakie oferuje PHP_Depend. Znajdziemy tam tabelę, w której każdy wiersz opisuje jedną metrykę. Gdy wybierzemy już, która metryka nas najbardziej interesuje przechodzimy do metody apply. Na parametrze $node wywołujemy metodę getMetric i jako parametr podajemy nazwę metryki PHP_Depend. Metoda getMetric zwróci nam wartość, którą następnie możemy poddać analizie w samej metodzie apply. Klasa analizująca metrykę powinna być podobna do klasy poniżej:

A teraz o tworzeniu reguł bazujących AST

Prawdopodobnie najlepszą metodą analizy kodu jest użycie AST (Abstract Syntax Tree) dostarczonego razem z PHP_Ddpend.

Do dyspozycji mamy dwie metody (findChildrenOfType i getFirstChildOfType), które możemy wywołać z obiektu, który przekazywany jest jako parametr w metodzie apply.

Przykładowo jeśli chcemy znaleźć konkretne słowo kluczowe np. tak nie lubiane goto musimy użyć metody findChildrenOfType i jako parametr podać jej string o wartości GotoStatement. Skąd jednak wiadomo jaki parametr podać? Otóż w katalogu vendor/pdepend/pdepend/src/main/php/PDepend/Source/AST jest grupa plików odpowiadających za te zadania i jeśli chcemy wykorzystać, którąś z klas drzewa w parametrze podajemy jej nazwę bez przedrostka AST. I tak przykładowo gdybyśmy chcieli sprawdzić czy w danej metodzie wykorzystano zmienne globalne to w tym celu użyjemy klasy ASTGlobalStatement a metodę findChildrenOfType wywołamy w następujący sposób $node-> findChildrenOfType ('GlobalStatement') . Metoda (findChildrenOfType) zwróci obiekt, na którym będziemy mogli użyć pętli foreach. Gdy obiekt będzie zawierał jakieś elementy znaczy to, że w sprawdzanej metodzie ktoś użył goto. Wobec tego w samej pętli należy zgłosić błąd.

A teraz o tworzeniu własnych zestawów reguł

Gdy już skończymy pisać kod reguły czas stworzyć plik XML będący definicją grupy reguł, która będzie zawierać naszą nowo utworzoną regułę. Początek pliku jest następujący:

Pierwszy atrybut znacznika ruleset to name i jest to nazwa grupy reguł. Pozostałe atrybuty dla wszystkich grup są stałe.

Jako pierwszy znacznik w ruleset (jeszcze przed definicją jakiejkolwiek pojedynczej roli) powinien pojawić się znacznik description, w którym powinniśmy zawrzeć opis naszego zestawu reguł. Przykład:

Teraz należy opisać regułę, którą utworzyliśmy w PHP. Przykład takiego kodu poniżej:

Zaczynamy od znacznika rule jego pierwszym atrybutem jest name i jest to po prostu nazwa reguły. Następny atrybut message jest to informacja jaka wyświetla się w CLI wtedy gdy PHPMD wykryje błąd. Atrybut class to nazwa klasy sprawdzającej naszą regułę a externalInfoUrl to adres do strony, która zawiera dodatkowe informacje na temat naszej reguły.

Na chwilkę wrócę jeszcze do atrybutu message i jego wartości. Otóż nie jesteśmy skazani na sztywny tekst – możemy wstawić do niego placeholery, które zostaną zamienione na wartość. Wystarczy w atrybucie message wstawić cyfrę w klamrach (np {1}, {2} etc). To jaką cyfrę wstawisz ma znaczenie, ponieważ jest to indeks z tablicy. Tablicę tą podaje się jako drugi argument w metodzie addViolation. Przykład poniżej:

Dodatkowo możemy skonfigurować właściwości, do których będziemy mogli się odwoływać w klasie reguły. Aby zdefiniować właściwość w znaczniku rule dodajemy znacznik properties a następnie property. Do tego znacznika musimy dodać atrybuty name (nazwa właściwości – będziemy się do niej odwoływać w kodzie PHP), value (wartość), description (opis).

Aby uzyskać dostęp do zdefiniowanych w pliku XML właściwości należy użyć następującego kodu: $this->getIntProperty('prop_name'); .

W całości plik ze zdefiniowaną przez nas regułą prezentuje się tak jak poniżej:

To jednak nie wszystko co można zrobić za pomocą tego rule. Oprócz definiowania reguł można za jego pomocą importować już istniejące reguły. Wystarczy wskazać w atrybucie ref adres pliku xml z grupą reguł, które chcemy zaimportować <rule ref="rulesets/unusedcode.xml" /> . Istnieje możliwość importu tylko jednej reguły. W takim przypadku w atrybucie ref po adresie pliku robimy slash i podajemy nazwę reguły, która nas interesuje ( <rule ref="rulesets/codesize.xml/CyclomaticComplexity" /> ).

Co zrobić w przypadku gdy nie chcemy zaimportować z grupy wszystkich reguł z wyjątkiem jednej? Można rzecz jasna zaimportować wszystkie reguły pojedynczo ale mamy także możliwość wykluczania reguł. Wystarczy użyć znacznika exlude wewnątrz znacznika rule tak jak poniżej:

Gdy już zaimportujemy dokładnie te reguły, które chcieliśmy możemy nadpisać ich właściwości. Dokonać tego możemy dokładnie w ten sam sposób jak w przypadku definiowania właściwości dla nowych reguł – poprzez znacznik properties wewnątrz znacznika rule. Istnieje także możliwość zmiany priorytetu dla konkretnej zaimportowanej reguły w taki sam sposób jak robi się to dla nowych reguł.

A teraz jak z tego wszystkiego skorzystać?

Gdy już zakończyliśmy pracę nad regułami i nad grupami reguł przyszedł czas wykorzystania ich w praktyce. Aby to zrobić należy przejść do CLI i wpisać następujące polecenie:

Oczywiście po przecinku można dodać inne grupy reguł. I to wszystko.

A teraz o istniejących zestawach reguł

Jak widać tworzenie nowych reguł nie jest zadaniem trudnym. Skoro tak to może już ktoś wcześniej napisał i udostępnił jakiś zestaw reguł, którego aktualnie potrzebuje? I tak też jest. W 5 minut udało mi się znaleźć ten zestaw reguł więc istnieje pewne prawdopodobieństwo, że to czego aktualnie potrzebujesz też znajduje się na GitHubie.

Przyznaje także, że z barku czasu nie testowałem powyższego zestawu reguł i nie jestem w stanie powiedzieć czy te reguły działają prawidłowo. Zachęcam także do dzielenia się w komentarzach jakie wam udało się znaleźć zestawy reguł w internecie.

A teraz podsumowanie

Tym postem kończę temat PHPMD. Mam nadzieję, że rozwiałem wszystkie wasze ewentualne wątpliwości, które pojawiły się po wcześniejszym wpisie lub w trakcie czytania tego postu.

Jeśli mimo tego macie jakieś pytania lub wątpliwości to zachęcam do komentowania – postaram się pomóc.

P.S. Bardzo dziękuję Marcinowi Tomczykowi za wskazanie błędów w tym poście i nieścisłości wraz z rozwiązaniami

Posty które mogą Cię zainteresować:

Dodaj komentarz

Your email address will not be published. Please enter your name, email and a comment.