Przejdź do treści
smallPict

Engineering17 min read

CI/CD dla wtyczki WordPress: WPCS, PHPStan i PHP od 7.4 do 8.5

CI/CD wtyczki WordPress w GitHub Actions: WPCS, PHPStan i testy automatyczne na PHP od 7.4 do 8.5 oraz bramka przed wdrożeniem na WordPress.org.

smallPict Engineering

Engineering

Wtyczka smallPict dla WordPressa konwertuje przesyłane obrazy do WebP i AVIF i serwuje je odwiedzającym. Gdy oznaczamy wydanie tagiem, GitHub Actions commituje je do repozytorium SVN na WordPress.org, a stamtąd trafia ono do każdej witryny z zainstalowaną wtyczką. Witryny z włączonymi automatycznymi aktualizacjami instalują je, zanim ktokolwiek przejrzy zmianę. Wtyczka deklaruje Requires PHP: 7.4 i Requires at least: 5.9, więc jeden build musi działać zarówno na hostingu współdzielonym, który wciąż ma PHP 7.4, jak i w kontenerze z PHP 8.5.

Błąd krytyczny we wtyczce może położyć wp-admin. Wartość bez escapowania na stronie ustawień to błąd XSS na wszystkich witrynach naraz. Brak weryfikacji nonce zamienia akcję administratora w cel ataku CSRF. Żadnego z tych błędów nie da się załatać po cichu: poprawka wymaga kolejnego wydania, a część witryn zostaje na zepsutej wersji, dopóki ono nie dotrze.

Ten wpis opisuje nasz pipeline CI/CD dla wtyczki WordPress: WordPress Coding Standards (WPCS), PHPStan i testy automatyczne uruchamiane na siedmiu wersjach PHP oraz bramkę przed wdrożeniem na WordPress.org. Wszystko poniżej pochodzi z repozytorium smallpict-wp w wydaniu 1.2.9.

Najważniejsze wnioski

  • Każdy pull request do master uruchamia w GitHub Actions równolegle WPCS, PHPStan i 9 skryptów testów automatycznych na PHP 7.4, 8.0, 8.1, 8.2, 8.3, 8.4 i 8.5.
  • WPCS wymusza sanityzację wejścia, escapowanie wyjścia, nonce i przygotowane zapytania. PHPStan na poziomie 5 znajduje błędy typów i martwe gałęzie na podstawie stubów WordPressa, bez uruchamiania WordPressa.
  • Tag wydania ponownie uruchamia WPCS na wszystkich siedmiu wersjach PHP, a zadanie, które commituje do SVN na WordPress.org, startuje dopiero wtedy, gdy przejdzie wszystkie siedem.

Pipeline CI/CD wtyczki WordPress w skrócie

Wtyczki pilnują dwa workflow w GitHub Actions.

WorkflowUruchamiany przyZadaniaSprawdzenia
phpcs.yml (PHP Code Quality & Standards)push do master lub develop, pull requesty do masterjedno zadanie na wersję PHP: 7.4, 8.0, 8.1, 8.2, 8.3, 8.4, 8.5WPCS, PHPStan, testy automatyczne
deploy-all.yml (Deploy to WordPress.org)push tagu v*WPCS na każdej z 7 wersji PHP, potem deployWPCS, potem build i commit do SVN

Uwaga o zakresie wersji PHP: Zrzuty ekranu w tym wpisie pokazują nasze wcześniejsze przebiegi CI obejmujące PHP od 7.4 do 8.3. Obecnie macierz testów w GitHub Actions obejmuje PHP od 7.4 do PHP 8.5, co zapewnia stałą zgodność z nowymi wersjami PHP bez regresji w starszych środowiskach.

Wszystkie narzędzia są zależnościami deweloperskimi Composera, więc nic z nich nie trafia do użytkowników:

JSON
"require-dev": {    "wp-coding-standards/wpcs": "^3.4.1",    "phpcsstandards/php_codesniffer": "^3.0",    "phpstan/phpstan": "^2.1",    "szepeviktor/phpstan-wordpress": "^2.0 || ^1.3.5",    "phpstan/extension-installer": "^1.4"}

Testy na macierzy wersji PHP w GitHub Actions

Co się zmienia między PHP 7.4 a PHP 8.5

PHP 7.4 i PHP 8.5 wykonują ten sam kod wtyczki w różny sposób. Oto niektóre zmiany, które dotyczą kodu wtyczek WordPress:

  • PHP 8.0 sprawił, że wiele funkcji wbudowanych rzuca TypeError dla argumentu złego typu, podczas gdy 7.4 zwracał null z ostrzeżeniem. Kod, który przekazywał tablicę do strlen(), działał dalej na 7.4, a na 8.0 się zatrzymuje.
  • PHP 8.0 zmienił sposób porównywania liczby z nienumerycznym ciągiem znaków. 0 == "abc" daje true na 7.4 i false na 8.0, co po cichu odwraca wynik niektórych luźnych porównań na wartościach opcji.
  • PHP 8.0 dodał str_starts_with() i pokrewne funkcje. Nasz procesor obrazów wywołuje str_starts_with(). Na PHP 7.4 działa to tylko dlatego, że WordPress 5.9 i nowsze zawierają polyfill, a to gwarantuje nasze Requires at least: 5.9.
  • PHP 8.1 oznaczył jako przestarzałe przekazywanie null do parametrów funkcji wbudowanych, które nie dopuszczają null. strtolower( wp_parse_url( $url, PHP_URL_HOST ) ) działa bez zastrzeżeń na 7.4, a na 8.1 zapisuje w logu deprecation, gdy $url jest względny, a host ma wartość null. Niektóre hostingi wypisują komunikaty deprecation prosto na stronę.
  • PHP 8.2 oznaczył jako przestarzałe właściwości dynamiczne. Przypisanie do niezadeklarowanej właściwości jednej z Twoich własnych klas zapisuje deprecation w logu przy każdym żądaniu.

Każda z tych zmian wychodzi na jaw dopiero wtedy, gdy kod jest analizowany lub wykonywany na wersji PHP, która zachowuje się inaczej. Dlatego pipeline uruchamia każdy pull request do master jednocześnie na wszystkich siedmiu wersjach.

Macierz w GitHub Actions

Przebieg CI wtyczki WordPress w GitHub Actions: testy na macierzy PHP z pięcioma równoległymi zadaniami WPCS i PHPStan na PHP 7.4, 8.0, 8.1, 8.2 i 8.3, wszystkie zakończone sukcesem

Rysunek 1: Workflow PHP Code Quality & Standards dla scalenia prac nad 1.2.0 do master. Każda wersja PHP to osobne zadanie, a zadanie PHP 8.3 pokazuje kroki, które wykonuje każde zadanie: PHPCS, PHPStan, a potem testy automatyczne.

Uwaga o zakresie wersji PHP: Powyższe zrzuty ekranu pokazują nasze wcześniejsze przebiegi CI obejmujące PHP od 7.4 do 8.3. Obecnie macierz testów w GitHub Actions obejmuje PHP od 7.4 do PHP 8.5, co zapewnia stałą zgodność z nowymi wersjami PHP bez regresji w starszych środowiskach.

YAML
jobs:  code-quality:    name: WPCS & PHPStan (PHP ${{ matrix.php-version }})    runs-on: ubuntu-26.04    strategy:      matrix:        php-version: ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5']    steps:      - uses: actions/checkout@v7      - uses: shivammathur/setup-php@v2        with:          php-version: ${{ matrix.php-version }}          tools: composer:v2, cs2pr          ini-values: memory_limit=2G          coverage: none      - run: composer install --prefer-dist --no-progress --no-interaction      - name: Run PHPCS (WordPress Coding Standards)        run: vendor/bin/phpcs -q --standard=phpcs.xml.dist --report=checkstyle | cs2pr      - name: Run PHPStan (WordPress Static Analysis)        run: vendor/bin/phpstan analyse --memory-limit=2G      - name: Run the standalone tests        run: |          for t in tests/test_*.php; do            echo "== $t"            php "$t" || exit 1          done

fail-fast zachowuje domyślną wartość true, więc gdy jedna wersja zawiedzie, GitHub anuluje zadania, które jeszcze trwają. Zepsuty pull request kosztuje niewiele, za cenę tego, że w jednym przebiegu nie widać wyników wszystkich wersji.

Czego dowodzi macierz, a czego nie

Trzy sprawdzenia nie zyskują na macierzy w tym samym stopniu. PHPStan analizuje kod pod kątem wersji PHP, na której działa, a testy wykonują kod na tej wersji, więc oba mogą dać inny wynik w każdym zadaniu. PHPCS pracuje na tokenach i w praktyce daje ten sam wynik na wszystkich siedmiu wersjach; w macierzy dowodzi głównie tego, że narzędzia instalują się i działają na każdej wersji. Sprawdzanie składni i funkcji względem zakresu wersji PHP w jednym przebiegu to zadanie PHPCompatibility, którego nasz zestaw reguł jeszcze nie zawiera.

Automatyzacja WordPress Coding Standards (WPCS)

Sniffy bezpieczeństwa ważne dla wtyczki

WPCS to zestaw sniffów PHP_CodeSniffer napisanych dla WordPressa. Reguły najważniejsze dla bezpieczeństwa wtyczki znajdują się w samym zestawie reguł WordPress:

  • WordPress.Security.ValidatedSanitizedInput zgłasza każdy odczyt $_POST, $_GET, $_REQUEST lub $_COOKIE, który nie przeszedł unslash i sanityzacji.
  • WordPress.Security.EscapeOutput zgłasza każde echo lub print wartości, która nie przeszła przez funkcję escapującą, taką jak esc_html(), esc_attr() lub esc_url().
  • WordPress.Security.NonceVerification zgłasza handler, który odczytuje dane żądania bez weryfikacji nonce.
  • WordPress.DB.PreparedSQL i PreparedSQLPlaceholders zgłaszają zapytanie zbudowane przez konkatenację ciągów zamiast $wpdb->prepare() oraz placeholdery, które nie pasują do swoich argumentów.

Nasz zestaw reguł WPCS

phpcs.xml.dist dodaje WordPress-Extra i WordPress-Docs, ustala text domain dla sniffów tłumaczeń i wymaga, aby każda globalna funkcja, klasa i stała miała nasz prefiks. Dzięki temu nazwy wtyczki nie kolidują z nazwami innej wtyczki na tej samej witrynie:

XML
<rule ref="WordPress">    <exclude name="Universal.Arrays.DisallowShortArraySyntax"/></rule><rule ref="WordPress-Extra"/><rule ref="WordPress-Docs"/>
<rule ref="WordPress.WP.I18n">    <properties>        <property name="text_domain" type="array">            <element value="smallpict"/>        </property>    </properties></rule>
<rule ref="WordPress.NamingConventions.PrefixAllGlobals">    <properties>        <property name="prefixes" type="array">            <element value="smallpict"/>            <element value="SmallPict"/>            <element value="SMALLPICT"/>        </property>    </properties></rule>

Handler AJAX, który przechodzi WPCS

Ten handler z wtyczki przywraca partiami media przeniesione przez offload. Każda granica wejścia jest obsłużona w jego pierwszych liniach:

PHP
public function ajax_restore_offloaded() {    if ( ! check_ajax_referer( 'smallpict_restore_offloaded', 'nonce', false ) || ! current_user_can( 'manage_options' ) ) {        wp_send_json_error( array( 'message' => 'forbidden' ), 403 );    }    global $wpdb;    $after = isset( $_POST['after'] ) ? absint( wp_unslash( $_POST['after'] ) ) : 0;    // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- admin action, the rows change on every call.    $ids = $wpdb->get_col(        $wpdb->prepare(            "SELECT DISTINCT post_id FROM {$wpdb->postmeta} WHERE meta_key = %s AND meta_value = %s AND post_id > %d ORDER BY post_id ASC LIMIT %d",            '_smallpict_offloaded',            '1',            $after,            self::RESTORE_BATCH        )    );    // ...}

Nonce i uprawnienie (capability) są sprawdzane razem, bo poprawny nonce dowodzi, że żądanie przyszło z naszej strony administracyjnej, a nie tego, że użytkownik może wykonać akcję. $_POST['after'] przechodzi unslash i rzutowanie przez absint(), zanim trafi do zapytania, a zapytanie i tak przechodzi przez $wpdb->prepare(), choć wartość jest już liczbą całkowitą.

Wyciszenie wymaga nazwy sniffa i powodu

Linia phpcs:ignore powyżej jest celowa. WPCS ostrzega przed bezpośrednimi zapytaniami do bazy danych bez cache'owania obiektów. To dobra rada dla odczytów na froncie, ale zła dla akcji administratora, której wiersze zmieniają się przy każdym wywołaniu. Nasza zasada: każdy ignore podaje dokładną nazwę sniffa i powód po --. Gołe // phpcs:ignore nie przechodzi code review.

W CI PHPCS zapisuje raport checkstyle, a cs2pr zamienia go w adnotacje na diffie pull requesta, więc recenzent widzi każde naruszenie w linii, która je spowodowała, a nie w logu.

Wykrywanie błędów logicznych analizą statyczną PHPStan

WPCS czyta kod linia po linii. Wie, że przy echo brakuje wywołania escapującego, ale nie wie, jaki typ ma zmienna ani czy dana gałąź może się kiedykolwiek wykonać. PHPStan buduje model typów przepływających przez każdą funkcję i zgłasza kod, który mu przeczy: wywołanie nieistniejącej metody, przekazanie string|false tam, gdzie wymagany jest string, porównanie wartości, które nigdy nie mogą być równe, warunki zawsze prawdziwe lub zawsze fałszywe.

PHPStan z WordPressem bez uruchamiania WordPressa

szepeviktor/phpstan-wordpress dostarcza PHPStanowi sygnatury funkcji i klas rdzenia WordPressa w plikach stubów oraz kilka rozszerzeń typów specyficznych dla WordPressa. phpstan/extension-installer rejestruje je automatycznie. PHPStan czyta te deklaracje i nigdy nie ładuje WordPressa ani bazy danych.

Nasza konfiguracja PHPStan

YAML
parameters:    level: 5    paths:        - smallpict.php        - includes/        - admin/    treatPhpDocTypesAsCertain: false    bootstrapFiles:        - phpstan-bootstrap.php

Poziom 5 sprawdza typ każdego argumentu przekazanego do funkcji lub metody, oprócz sprawdzeń z niższych poziomów: nieznanych klas, metod i właściwości, typów zwracanych i martwego kodu. treatPhpDocTypesAsCertain: false mówi PHPStanowi, żeby nie traktował naszych własnych docblocków @param jako dowodu. WordPress przekazuje to, co zwróci filtr, więc parametr udokumentowany jako int może przyjść jako string z innej wtyczki, a my chcemy, żeby PHPStan zostawił sprawdzenia obronne, które to obsługują. Plik bootstrap definiuje stałe wtyczki (SMALLPICT_PATH, SMALLPICT_URL i inne), które normalnie są ustawiane w czasie działania.

Log analizy statycznej PHPStan dla WordPressa w GitHub Actions: krok WPCS bez uwag, PHPStan przeanalizował 11 z 11 plików wtyczki z wynikiem „[OK] No errors”

Rysunek 2: Kroki PHPCS i PHPStan w zadaniu PHP 8.3. PHPCS nic nie wypisuje, gdy nie ma czego zgłosić. PHPStan analizuje 11 plików wtyczki (główny plik wtyczki i 10 klas w includes/) i nie zgłasza błędów.

Prawdziwe znalezisko PHPStan: linia 626 procesora obrazów

2 października 2026 roku pipeline zatrzymał się na PHPStan z jednym błędem:

PHPStan wyłapuje błąd we wtyczce WordPress: „Right side of && is always true” w includes/class-smallpict-image-processor.php, linia 626, reguła booleanAnd.rightAlwaysTrue, zadanie nieudane na PHP 8.2

Rysunek 3: Zadanie PHP 8.2 zatrzymało się na kroku PHPStan z booleanAnd.rightAlwaysTrue w linii 626 pliku includes/class-smallpict-image-processor.php. Wtedy wtyczka miała 9 plików w analizowanych ścieżkach.

Ten kod to ostatni krok konwersji. Gdy ustawienie wtyczki Format wyjściowy ma wartość WebP lub AVIF, przekonwertowany plik staje się załącznikiem, a oryginalny przesłany plik jest usuwany, żeby oszczędzić miejsce na dysku. Pozostałe opcje, „AVIF, z WebP jako zapasowym” i „Zachowaj oryginalny format”, zachowują oryginalne pliki JPEG i PNG. Tak wyglądała gałąź usuwania:

PHP
// C. Pure 1-to-1 Mode: Delete raw uncompressed source file to free 100% hosting space.if ( ( $is_single_webp || $is_single_avif ) && $canonical_path !== $main_file_path && file_exists( $main_file_path ) ) {    wp_delete_file( $main_file_path );}

PHPStan twierdził, że file_exists( $main_file_path ) jest w tym miejscu zawsze prawdziwe. Powód leży 150 linii wcześniej, na początku tej samej metody:

PHP
$main_file_path = get_attached_file( $attachment_id );if ( ! $main_file_path || ! file_exists( $main_file_path ) ) {    return new WP_Error( 'file_missing', 'File not found on disk.' );}

Metoda działa dalej tylko wtedy, gdy plik istnieje, a PHPStan przenosi ten fakt aż do linii 626.

Nie przyjęliśmy tego znaleziska bez sprawdzenia. file_exists() pyta system plików, a odpowiedź może się zmienić między dwoma wywołaniami. PHPStan domyślnie zapamiętuje wynik takiego wywołania (opcja rememberPossiblyImpureFunctionValues), zakładając, że w międzyczasie nic nie zmieniło stanu dysku. Zanim usunęliśmy sprawdzenie, potwierdziliśmy w kodzie dwie rzeczy:

  1. Nic między warunkiem ochronnym a linią 626 nie usuwa ani nie przenosi pliku źródłowego. Konwersja zapisuje każdy wynik do pliku tymczasowego i przenosi go pod nową ścieżkę, a jedyne wywołania wp_delete_file() na tej ścieżce usuwają pliki tymczasowe. Krok offloadu, który rzeczywiście usuwa pliki lokalne, wykonuje się po linii 626.
  2. wp_delete_file() i tak toleruje brak pliku. Przepuszcza ścieżkę przez filtr wp_delete_file i wywołuje @unlink(), które nie robi nic szkodliwego, gdy pliku już nie ma.

Skoro oba punkty się potwierdziły, warunek był martwym kodem, a martwy kod w gałęzi usuwania sugeruje następnemu czytelnikowi, że plik może zniknąć w trakcie metody. Poprawka go usunęła:

DIFF
-if ( ( $is_single_webp || $is_single_avif ) && $canonical_path !== $main_file_path && file_exists( $main_file_path ) ) {+if ( ( $is_single_webp || $is_single_avif ) && $canonical_path !== $main_file_path ) {     wp_delete_file( $main_file_path ); }

To znalezisko nie dotyczyło awarii. Jego wartość leżała w tym, co mówiło o metodzie: założenia kodu rozjechały się z jego strukturą, i to w jedynej gałęzi, która usuwa pliki użytkowników. Nasz wniosek jest węższy niż „ufaj analizatorowi”. Gdy analizator zgłasza „zawsze prawdziwe” lub „zawsze fałszywe” dla funkcji systemu plików, sieci lub czasu, przed usunięciem warunku trzeba ręcznie sprawdzić, co dzieje się między dwoma wywołaniami.

Testy automatyczne wtyczki WordPress bez PHPUnit

Ostatni krok każdego zadania macierzy uruchamia pliki z tests/. To nie są testy PHPUnit. Każdy plik to zwykły skrypt PHP, który definiuje minimalne stuby potrzebnych mu funkcji WordPressa (get_option(), update_option(), sanitize_key() i tak dalej), ładuje testowaną klasę wtyczki, wykonuje ją i kończy działanie z niezerowym kodem przy pierwszym błędzie:

Shell
for t in tests/test_.php do  echo "== $t"  php "$t"  exit 1done

Obecnie jest 9 takich skryptów. Obejmują ścieżki, na których pomyłka kosztuje klienta dane lub pieniądze: pipeline konwersji do WebP i AVIF wraz z wariantami rezerwowymi, dostarczanie obrazów, offload i usuwanie plików w magazynie w chmurze, przywracanie przeniesionych plików partiami, zachowanie, gdy konto przestaje płacić, limit przestrzeni i odinstalowanie. Test odinstalowania wykonuje prawdziwe zapytania deinstalacyjne na bazie SQLite w pamięci i sprawdza, co zostało:

PHP
check(    array( '_smallpict_cdn_base', '_smallpict_cdn_key', '_smallpict_offloaded', '_smallpictx', '_wp_attached_file' ) === $meta,    'offload markers and foreign meta remain (got ' . implode( ',', $meta ) . ')');

Ta asercja chroni decyzję, którą łatwo przypadkiem cofnąć. Odinstalowanie wtyczki usuwa jej ustawienia, ale zachowuje post meta, w których zapisano, które pliki istnieją tylko w magazynie w chmurze. Dzięki temu witryna, która ponownie zainstaluje wtyczkę, nadal znajdzie swoje obrazy. Nieostrożne DELETE ... LIKE '_smallpict%' usunęłoby te znaczniki, a wtedy test nie przejdzie.

Wybraliśmy zwykłe skrypty zamiast zestawu testów PHPUnit dla WordPressa ze względu na szybkość i koszt konfiguracji: nie potrzebują serwera bazy danych ani kopii WordPressa, a wszystkie 9 wykonuje się w mniej niż sekundę, więc uruchomienie ich na siedmiu wersjach PHP prawie nic nie dodaje do pull requesta. Kosztem jest to, że stuby modelują tylko to, co w nich zapisaliśmy. Zachowanie zależne od prawdziwego rdzenia WordPressa, na przykład własna obsługa metadanych w bibliotece mediów, nie jest objęte tymi testami.

Bramka przed wdrożeniem na WordPress.org

Wydania wychodzą przez osobny workflow. Uruchamia go push tagu, na przykład v1.2.9, a zadanie, które commituje do SVN na WordPress.org, deklaruje zależność od macierzy lintowania:

YAML
jobs:  lint:    name: WPCS Check (PHP ${{ matrix.php-version }})    runs-on: ubuntu-26.04    strategy:      matrix:        php-version: ['7.4', '8.0', '8.1', '8.2', '8.3', '8.4', '8.5']    steps:      - uses: actions/checkout@v7      - uses: shivammathur/setup-php@v2        with:          php-version: ${{ matrix.php-version }}      - run: composer install --prefer-dist --no-progress --no-interaction      - name: PHP CodeSniffer Check        run: vendor/bin/phpcs --standard=phpcs.xml.dist
  deploy:    name: Deploy    needs: lint    runs-on: ubuntu-26.04    permissions:      contents: read    steps:      - uses: actions/checkout@v7      - uses: shivammathur/setup-php@v2        with:          php-version: '7.4'      - name: Build WP.org Version        run: |          chmod +x build-wporg.sh          ./build-wporg.sh      - name: Deploy to WordPress.org        uses: 10up/action-wordpress-plugin-deploy@stable        with:          generate-zip: false        env:          SVN_USERNAME: ${{ secrets.SVN_USERNAME }}          SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }}          SLUG: smallpict          BUILD_DIR: build-wporg/smallpict          ASSETS_DIR: .wordpress-org

needs: lint oznacza, że zadanie deploy startuje dopiero wtedy, gdy wszystkie siedem zadań lint zakończy się sukcesem. Jeśli WPCS zawiedzie na którejkolwiek wersji, GitHub pomija deploy i commit do SVN nie następuje. Krok budowania działa na PHP 7.4, najstarszej wspieranej przez nas wersji. Token zadania ma dostęp do repozytorium tylko do odczytu; jedyny zapis trafia do SVN na WordPress.org, z danymi logowania przechowywanymi jako sekrety repozytorium.

Bramka przed wdrożeniem w workflow Deploy to WordPress.org w GitHub Actions: WPCS Check przeszedł na PHP 7.4, 8.0, 8.1, 8.2 i 8.3 przed uruchomieniem zadania Deploy

Rysunek 4: Workflow Deploy to WordPress.org. Najpierw uruchamia się pięć zadań WPCS Check; zadanie Deploy pod nimi startuje dopiero, gdy przejdą wszystkie pięć.

Bramka ponownie uruchamia WPCS na otagowanym commicie, zamiast ufać wcześniejszemu przebiegowi dla pull requesta. Tag może wskazywać dowolny commit, w tym wypchnięty bezpośrednio albo taki, którego sprawdzenia nigdy się nie zakończyły, a workflow wydania nie ma innego sposobu, by wiedzieć, co wypuszcza.

Lokalne uruchamianie sprawdzeń CI

Skrypty Composera opakowują te same polecenia, które uruchamia CI, więc błąd można odtworzyć przed pushem:

Shell
composer installcomposer lint        # vendor/bin/phpcs --standard=phpcs.xml.distcomposer lint-fix    # vendor/bin/phpcbf: fixes spacing and formatting sniffscomposer phpstan     # vendor/bin/phpstan analysefor t in tests/test_.php do php "$t"  break done

Aby odtworzyć błąd z jednego zadania macierzy, uruchom te same polecenia pod tą wersją PHP, na przykład w oficjalnym obrazie po composer install:

Shell
docker run --rm -v "$PWD":/app -w /app php:7.4-cli \  sh -c 'vendor/bin/phpstan analyse --memory-limit=2G && for t in tests/test_*.php; do php "$t" || exit 1; done'

phpcbf naprawia tylko to, co sniff może poprawić mechanicznie. W przypadku znalezisk dotyczących escapowania, sanityzacji i nonce człowiek musi zdecydować, która funkcja pasuje do kontekstu, i nie poprawiamy ich automatycznie.

Czego pipeline jeszcze nie obejmuje

Na naszej liście są trzy luki:

  1. Bramka przed wdrożeniem uruchamia tylko WPCS. PHPStan i testy automatyczne działają przy pushach do master i develop oraz przy pull requestach, ale nie przy tagu wydania. Lukę zamknie dodanie obu kroków do zadania lint w workflow wdrożenia.
  2. Nasz zestaw reguł nie zawiera PHPCompatibility. Dodanie phpcompatibility/phpcompatibility-wp z testVersion ustawionym na 7.4- pozwoliłoby w jednym przebiegu zgłaszać składnię i funkcje, które nie istnieją w 7.4.
  3. Testy używają stubów zamiast prawdziwej instalacji WordPressa. Zadanie integracyjne na WordPressie 5.9 (nasze minimum) i najnowszym wydaniu obejmie zachowanie rdzenia, którego stuby nie odwzorują.

Najczęstsze pytania

Dlaczego testy na macierzy PHP od 7.4 do 8.5 są kluczowe dla wtyczek WordPress?

WordPress.org dostarcza to samo wydanie do każdej witryny, a nagłówek wtyczki (Requires PHP: 7.4) jest jedynym filtrem tego, na jakich wersjach PHP się ona instaluje. Między 7.4 a 8.5 PHP zmienił to, jak funkcje wbudowane traktują argumenty złego typu, jak liczby porównują się z ciągami znaków i które konstrukcje zgłaszają deprecation, więc kod poprawny na jednej wersji może na innej skończyć się błędem krytycznym lub ostrzeżeniami w logu. Przebieg CI na jednej wersji PHP dowodzi tylko tego, że wtyczka działa na tej wersji. Uruchamianie PHPStan i testów na każdej wspieranej wersji wyłapuje te różnice, zanim wydanie dotrze do użytkowników.

Czy WPCS zastępuje tradycyjny audyt bezpieczeństwa?

Nie. WPCS sprawdza wzorce w linii, w której występują: zmienną żądania odczytaną bez sanityzacji, echo bez escapowania, handler bez sprawdzenia nonce, zapytanie bez $wpdb->prepare(). Nie potrafi ocenić, czy handler sprawdza właściwe uprawnienie, czy funkcja escapująca pasuje do kontekstu wyjścia, czy żądanie po stronie serwera trafia pod URL kontrolowany przez użytkownika ani czy jeden użytkownik może dostać się do danych innego. Komentarz phpcs:ignore może też wyciszyć sniff. Traktujemy WPCS jako dolną granicę, która nie przepuszcza znanych błędów, a zmiany istotne dla bezpieczeństwa przeglądamy ręcznie.

Jak PHPStan obsługuje funkcje rdzenia WordPressa bez wykonywania kodu?

PHPStan nigdy nie uruchamia analizowanego kodu. Czyta deklaracje i docblocki, aby zbudować model typów. Dla WordPressa szepeviktor/phpstan-wordpress dostarcza pliki stubów: sygnatury, typy parametrów i typy zwracane funkcji i klas rdzenia WordPressa, bez ich ciał. PHPStan czyta te stuby tak, jakby były WordPressem, więc może sprawdzić, czy wywołanie wp_delete_file() lub get_attached_file() przekazuje i otrzymuje właściwe typy. Stałe, które WordPress lub wtyczka definiują w czasie działania, trafiają do pliku bootstrap, który PHPStan ładuje przed analizą.

Wtyczka jest dostępna na WordPress.org jako smallPict. Instalację i ustawienia „Format wyjściowy” opisują przewodnik instalacji i dokumentacja ustawień obrazów.

Autor: smallPict Engineering

Engineering

Tworzymy konwersję obrazów i dostarczanie przez CDN dla stron internetowych i aplikacji.

Wypróbuj na własnych obrazach.

20 MB miesięcznie za darmo. Około dwóch minut konfiguracji w WordPressie.

Więcej o wydajności obrazów, Core Web Vitals i magazynie w chmurze.

Wszystkie artykuły

Cloud & CDN3 min read

SmallPict API/SDK: BYOK czy zarządzany CDN – co wybrać?

Plan Ignite nie obejmuje CDN (wyniki wracają bezpośrednio do aplikacji), a w planach Velocity i Momentum wybierasz między magazynem i CDN SmallPict (domyślnie) a własnym magazynem i CDN. Oto jak zdecydować, który model pasuje do Twojego projektu.