Перейти к содержимому
smallPict

Engineering17 min read

CI/CD для плагина WordPress: WPCS, PHPStan и PHP от 7.4 до 8.5

CI/CD плагина WordPress в GitHub Actions: WPCS, PHPStan и автотесты на PHP от 7.4 до 8.5, плюс проверка перед деплоем на WordPress.org.

smallPict Engineering

Engineering

Плагин smallPict для WordPress конвертирует загружаемые изображения в WebP и AVIF и отдает их посетителям. Когда мы ставим тег релиза, GitHub Actions коммитит его в SVN-репозиторий WordPress.org, а оттуда он попадает на каждый сайт, где установлен плагин. Сайты с включенными автообновлениями устанавливают его, и никто не просматривает изменения. Плагин объявляет Requires PHP: 7.4 и Requires at least: 5.9, поэтому одна сборка должна работать и на виртуальном хостинге, который всё ещё на PHP 7.4, и в контейнере с PHP 8.5.

Фатальная ошибка в плагине может положить wp-admin. Неэкранированное значение на странице настроек означает XSS сразу на всех сайтах. Пропущенная проверка nonce превращает действие администратора в цель для CSRF. Ничего из этого нельзя исправить незаметно: для исправления нужен новый релиз, и часть сайтов остается на сломанной версии, пока он не выйдет.

В этой статье описан наш CI/CD-пайплайн для плагина WordPress: WordPress Coding Standards (WPCS), PHPStan и автотесты, которые запускаются на семи версиях PHP, и проверка перед деплоем на WordPress.org. Всё, что описано ниже, взято из репозитория smallpict-wp на релизе 1.2.9.

Главное

  • Каждый pull request в master параллельно запускает в GitHub Actions WPCS, PHPStan и 9 скриптов автотестов на PHP 7.4, 8.0, 8.1, 8.2, 8.3, 8.4 и 8.5.
  • WPCS требует санитизации ввода, экранирования вывода, nonce и подготовленных запросов. PHPStan на уровне 5 находит ошибки типов и мертвые ветки по стабам WordPress, не запуская WordPress.
  • Тег релиза снова запускает WPCS на всех семи версиях PHP, и задание, которое коммитит в SVN WordPress.org, стартует только после того, как пройдут все семь.

CI/CD-пайплайн плагина WordPress в общих чертах

Плагин защищают два workflow в GitHub Actions.

WorkflowКогда запускаетсяЗаданияПроверки
phpcs.yml (PHP Code Quality & Standards)push в master или develop, pull request в masterпо одному заданию на версию PHP: 7.4, 8.0, 8.1, 8.2, 8.3, 8.4, 8.5WPCS, PHPStan, автотесты
deploy-all.yml (Deploy to WordPress.org)push тега v*WPCS на каждой из 7 версий PHP, затем deployWPCS, затем сборка и коммит в SVN

Примечание о покрытии версий PHP: Скриншоты в этой статье показывают наши более ранние прогоны CI на PHP от 7.4 до 8.3. Сейчас тестовая матрица в GitHub Actions расширена до PHP от 7.4 до PHP 8.5, что обеспечивает постоянную совместимость с современными средами выполнения PHP без регрессий в старых окружениях.

Все инструменты подключены как dev-зависимости Composer, поэтому ничего из них не попадает к пользователям:

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"}

Тестирование на матрице версий PHP в GitHub Actions

Что меняется между PHP 7.4 и PHP 8.5

PHP 7.4 и PHP 8.5 выполняют один и тот же исходный код плагина по-разному. Вот некоторые изменения, которые затрагивают код плагинов WordPress:

  • В PHP 8.0 многие встроенные функции стали выбрасывать TypeError при аргументе неверного типа, тогда как 7.4 возвращал null с предупреждением. Код, который передавал массив в strlen(), продолжал работать на 7.4 и останавливается на 8.0.
  • В PHP 8.0 изменилось сравнение числа с нечисловой строкой. 0 == "abc" дает true на 7.4 и false на 8.0, из-за чего часть нестрогих сравнений со значениями опций молча меняет результат на противоположный.
  • В PHP 8.0 появились str_starts_with() и родственные функции. Наш обработчик изображений вызывает str_starts_with(). На PHP 7.4 это работает только потому, что WordPress 5.9 и новее содержат полифил, а его наличие гарантирует наш Requires at least: 5.9.
  • В PHP 8.1 передача null в параметры встроенных функций, не допускающие null, объявлена устаревшей. strtolower( wp_parse_url( $url, PHP_URL_HOST ) ) работает без замечаний на 7.4 и пишет в лог deprecation на 8.1, когда $url относительный и хост равен null. Некоторые хостинги выводят deprecation-сообщения прямо на страницу.
  • В PHP 8.2 динамические свойства объявлены устаревшими. Присваивание необъявленному свойству одного из ваших собственных классов пишет deprecation в лог на каждом запросе.

Каждое из этих изменений проявляется только тогда, когда код анализируется или выполняется на той версии PHP, которая ведет себя иначе. Поэтому пайплайн прогоняет каждый pull request в master сразу на всех семи версиях.

Матрица GitHub Actions

Запуск CI плагина WordPress в GitHub Actions: тестирование на матрице PHP, пять параллельных заданий WPCS и PHPStan на PHP 7.4, 8.0, 8.1, 8.2 и 8.3, все успешны

Рисунок 1: Workflow PHP Code Quality & Standards для слияния работы над 1.2.0 в master. Каждая версия PHP выполняется отдельным заданием, а задание PHP 8.3 показывает шаги, которые выполняет каждое задание: PHPCS, PHPStan, затем автотесты.

Примечание о покрытии версий PHP: Скриншоты выше показывают наши более ранние прогоны CI на PHP от 7.4 до 8.3. Сейчас тестовая матрица в GitHub Actions расширена до PHP от 7.4 до PHP 8.5, что обеспечивает постоянную совместимость с современными средами выполнения PHP без регрессий в старых окружениях.

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 сохраняет значение по умолчанию true, поэтому, когда одна версия падает, GitHub отменяет еще выполняющиеся задания. Сломанный pull request обходится дешево, но в одном запуске вы видите результаты не всех версий.

Что матрица доказывает, а что нет

Три проверки получают от матрицы разную пользу. PHPStan анализирует код для той версии PHP, на которой запущен, а тесты выполняют код на этой версии, поэтому оба могут дать разный ответ в каждом задании. PHPCS работает с токенами и на практике дает одинаковый ответ на всех семи версиях; в матрице он в основном доказывает, что инструменты устанавливаются и запускаются на каждой версии. Проверять синтаксис и функции по диапазону версий PHP за один проход должен PHPCompatibility, которого в нашем наборе правил пока нет.

Автоматизация WordPress Coding Standards (WPCS)

Сниффы безопасности, важные для плагина

WPCS представляет собой набор сниффов PHP_CodeSniffer, написанных для WordPress. Правила, которые важнее всего для безопасности плагина, находятся в самом наборе правил WordPress:

  • WordPress.Security.ValidatedSanitizedInput отмечает любое чтение $_POST, $_GET, $_REQUEST или $_COOKIE, которое не прошло unslash и санитизацию.
  • WordPress.Security.EscapeOutput отмечает любой echo или print значения, которое не прошло через функцию экранирования, например esc_html(), esc_attr() или esc_url().
  • WordPress.Security.NonceVerification отмечает обработчик, который читает данные запроса без проверки nonce.
  • WordPress.DB.PreparedSQL и PreparedSQLPlaceholders отмечают запрос, собранный конкатенацией строк вместо $wpdb->prepare(), и плейсхолдеры, которые не совпадают со своими аргументами.

Наш набор правил WPCS

phpcs.xml.dist добавляет WordPress-Extra и WordPress-Docs, фиксирует text domain для сниффов переводов и требует, чтобы каждая глобальная функция, класс и константа имели наш префикс. Так плагин не может конфликтовать с именами другого плагина на том же сайте:

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>

AJAX-обработчик, который проходит WPCS

Этот обработчик из плагина пакетами восстанавливает выгруженные (offloaded) медиафайлы. Все входные границы обрабатываются в его первых строках:

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 и права (capability) проверяются вместе, потому что валидный nonce доказывает, что запрос пришел с нашей страницы администратора, но не то, что пользователю разрешено выполнить действие. $_POST['after'] проходит unslash и приводится через absint() до того, как попадет в запрос, а запрос всё равно идет через $wpdb->prepare(), хотя значение уже целое число.

Подавление требует имени сниффа и причины

Строка phpcs:ignore выше оставлена намеренно. WPCS предупреждает о прямых запросах к базе данных без объектного кеширования. Для чтения на фронтенде это хороший совет, а для действия администратора, строки которого меняются при каждом вызове, неверный. Наше правило: каждый ignore называет конкретный снифф и указывает причину после --. Голый // phpcs:ignore не проходит ревью.

В CI PHPCS пишет отчет в формате checkstyle, а cs2pr превращает его в аннотации на диффе pull request, так что ревьюер видит каждое нарушение на строке, которая его вызвала, а не в логе.

Поиск логических ошибок статическим анализом PHPStan

WPCS читает код построчно. Он знает, что у echo нет вызова экранирования, но не знает, какой тип хранит переменная и может ли ветка когда-нибудь выполниться. PHPStan строит модель типов, которые проходят через каждую функцию, и сообщает о коде, который ей противоречит: вызов несуществующего метода, передача string|false туда, где требуется string, сравнение значений, которые никогда не могут быть равны, условия, которые всегда истинны или всегда ложны.

PHPStan для WordPress без запуска WordPress

szepeviktor/phpstan-wordpress передает PHPStan сигнатуры функций и классов ядра WordPress через файлы-стабы, а также несколько расширений типов, специфичных для WordPress. phpstan/extension-installer регистрирует его автоматически. PHPStan читает эти объявления и никогда не загружает WordPress или базу данных.

Наша конфигурация PHPStan

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

Уровень 5 проверяет тип каждого аргумента, переданного в функцию или метод, вдобавок к проверкам нижних уровней: неизвестные классы, методы и свойства, возвращаемые типы и мертвый код. treatPhpDocTypesAsCertain: false говорит PHPStan не считать наши собственные докблоки @param доказательством. WordPress передает то, что вернул фильтр, поэтому параметр, задокументированный как int, может прийти строкой из другого плагина, и мы хотим, чтобы PHPStan сохранял защитные проверки на этот случай. Bootstrap-файл определяет константы плагина (SMALLPICT_PATH, SMALLPICT_URL и другие), которые обычно задаются во время выполнения.

Лог статического анализа PHPStan для WordPress в GitHub Actions: шаг WPCS без замечаний, PHPStan проанализировал 11 из 11 файлов плагина с результатом «[OK] No errors»

Рисунок 2: Шаги PHPCS и PHPStan в задании PHP 8.3. PHPCS ничего не выводит, когда сообщать не о чем. PHPStan анализирует 11 файлов плагина (основной файл плагина и 10 классов в includes/) и не находит ошибок.

Реальная находка PHPStan: строка 626 обработчика изображений

2 октября 2026 года пайплайн упал на PHPStan с одной ошибкой:

PHPStan находит баг в плагине WordPress: «Right side of && is always true» в includes/class-smallpict-image-processor.php, строка 626, правило booleanAnd.rightAlwaysTrue, задание упало на PHP 8.2

Рисунок 3: Задание PHP 8.2 остановилось на шаге PHPStan с booleanAnd.rightAlwaysTrue в строке 626 файла includes/class-smallpict-image-processor.php. На тот момент в анализируемых путях у плагина было 9 файлов.

Этот код выполняет последний шаг конвертации. Когда в настройке плагина Выходной формат выбран WebP или AVIF, сконвертированный файл становится вложением, а исходная загрузка удаляется, чтобы сэкономить место на диске. Другие варианты, «AVIF с WebP в качестве резерва» и «Сохранять исходный формат», сохраняют исходные файлы JPEG и PNG. Ветка удаления выглядела так:

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 утверждал, что file_exists( $main_file_path ) в этой точке всегда истинно. Причина находится на 150 строк выше, в начале того же метода:

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.' );}

Метод продолжает работу, только если файл существует, и PHPStan переносит этот факт до строки 626.

Мы не приняли находку на веру. file_exists() обращается к файловой системе, и ответ может измениться между двумя вызовами. PHPStan по умолчанию запоминает результат такого вызова (опция rememberPossiblyImpureFunctionValues), исходя из того, что между вызовами на диске ничего не изменилось. Прежде чем удалить проверку, мы прочитали код и убедились в двух вещах:

  1. Между проверкой в начале метода и строкой 626 ничто не удаляет и не перемещает исходный файл. Конвертация пишет каждый результат во временный файл и перемещает его по новому пути, а единственные вызовы wp_delete_file() для этого пути удаляют временные файлы. Шаг выгрузки, который действительно удаляет локальные файлы, выполняется после строки 626.
  2. wp_delete_file() в любом случае спокойно обрабатывает отсутствующий файл. Она пропускает путь через фильтр wp_delete_file и вызывает @unlink(), который ничего не ломает, если файла уже нет.

Раз оба пункта подтвердились, условие было мертвым кодом, а мертвый код в ветке удаления вводит следующего читателя в заблуждение: кажется, что файл может исчезнуть посреди метода. Исправление его убрало:

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 ); }

Находка не касалась падения. Ее ценность в том, что она показала про метод: допущения кода разошлись с его структурой, причем в единственной ветке, которая удаляет файлы пользователей. Наш вывод уже, чем «доверяй анализатору». Если анализатор сообщает «всегда истинно» или «всегда ложно» для функции файловой системы, сети или времени, прежде чем удалять условие, нужно вручную проверить, что происходит между двумя вызовами.

Автотесты для плагина WordPress без PHPUnit

Последний шаг каждого задания матрицы запускает файлы из tests/. Это не тесты PHPUnit. Каждый файл представляет собой обычный PHP-скрипт, который определяет минимальные стабы для нужных ему функций WordPress (get_option(), update_option(), sanitize_key() и так далее), загружает тестируемый класс плагина, прогоняет его и завершается с ненулевым кодом при первой ошибке:

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

Сейчас таких скриптов 9. Они покрывают места, где ошибка стоит клиенту данных или денег: пайплайн конвертации в WebP и AVIF и его резервные варианты, доставку изображений, выгрузку и удаление файлов в облачном хранилище, пакетное восстановление выгруженных файлов, поведение, когда аккаунт перестает платить, квоту хранилища и удаление плагина. Тест удаления выполняет настоящие запросы деинсталляции на SQLite-базе в памяти и проверяет, что осталось:

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 ) . ')');

Эта проверка защищает решение, которое легко случайно отменить. При удалении плагин стирает свои настройки, но сохраняет post meta, где записано, какие файлы хранятся только в облачном хранилище, чтобы сайт, переустановивший плагин, всё ещё мог найти свои изображения. Неосторожный DELETE ... LIKE '_smallpict%' удалил бы эти маркеры, и в этом случае тест падает.

Мы выбрали обычные скрипты вместо набора тестов WordPress на PHPUnit ради скорости и простоты настройки: им не нужен сервер базы данных и копия WordPress, а все 9 выполняются меньше чем за секунду, так что запуск на семи версиях PHP почти не удлиняет проверку pull request. Обратная сторона в том, что стабы моделируют только то, что мы в них написали. Поведение, которое зависит от настоящего ядра WordPress, например собственная обработка метаданных в медиатеке, эти тесты не покрывают.

Проверка перед деплоем на WordPress.org

Релизы выходят через отдельный workflow. Его запускает push тега, например v1.2.9, и задание, которое коммитит в SVN WordPress.org, объявляет зависимость от матрицы линтинга:

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 означает, что задание deploy стартует только после успеха всех семи заданий lint. Если WPCS падает на любой версии, GitHub пропускает deploy, и коммита в SVN не происходит. Шаг сборки выполняется на PHP 7.4, самой старой из поддерживаемых нами версий. Токен задания имеет доступ к репозиторию только на чтение; единственная запись идет в SVN WordPress.org, с учетными данными из секретов репозитория.

Проверка перед деплоем в workflow Deploy to WordPress.org в GitHub Actions: WPCS Check прошел на PHP 7.4, 8.0, 8.1, 8.2 и 8.3 до запуска задания Deploy

Рисунок 4: Workflow Deploy to WordPress.org. Сначала выполняются пять заданий WPCS Check; задание Deploy под ними запускается только после того, как пройдут все пять.

Проверка заново запускает WPCS на коммите с тегом и не полагается на более ранний прогон pull request. Тег может указывать на любой коммит, в том числе запушенный напрямую или такой, проверки которого так и не завершились, и у релизного workflow нет другого способа узнать, что именно он выпускает.

Локальный запуск проверок CI

Скрипты Composer оборачивают те же команды, что запускает CI, поэтому ошибку можно воспроизвести до push:

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

Чтобы воспроизвести ошибку из одного задания матрицы, запустите те же команды под этой версией PHP, например в официальном образе после 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 исправляет только то, что снифф может исправить механически. Для находок по экранированию, санитизации и nonce человек должен решить, какая функция подходит к контексту, поэтому мы не исправляем их автоматически.

Что пайплайн пока не покрывает

В нашем списке три пробела:

  1. Проверка перед деплоем запускает только WPCS. PHPStan и автотесты выполняются при push в master и develop и на pull request, но не на теге релиза. Это исправит добавление обоих шагов в задание lint workflow деплоя.
  2. Наш набор правил не включает PHPCompatibility. Если добавить phpcompatibility/phpcompatibility-wp с testVersion, равным 7.4-, синтаксис и функции, которых нет в 7.4, будут отмечаться за один запуск.
  3. Тесты используют стабы, а не настоящую установку WordPress. Интеграционное задание на WordPress 5.9 (наш минимум) и последнем релизе покроет поведение ядра, которое стабы воспроизвести не могут.

Вопросы и ответы

Почему тестирование на матрице PHP от 7.4 до 8.5 так важно для плагинов WordPress?

WordPress.org доставляет один и тот же релиз на каждый сайт, и заголовок плагина (Requires PHP: 7.4) остается единственным фильтром того, на каких версиях PHP его устанавливают. Между 7.4 и 8.5 в PHP изменилось то, как встроенные функции обрабатывают аргументы неверного типа, как числа сравниваются со строками и какие конструкции вызывают deprecation, поэтому код, корректный на одной версии, может вызвать фатальную ошибку или предупреждения на другой. Прогон CI на одной версии PHP доказывает только то, что плагин работает на этой версии. Запуск PHPStan и тестов на каждой поддерживаемой версии ловит эти различия до того, как релиз дойдет до пользователей.

Заменяет ли WPCS традиционный аудит безопасности?

Нет. WPCS проверяет шаблоны в той строке, где они встречаются: переменная запроса, прочитанная без санитизации, echo без экранирования, обработчик без проверки nonce, запрос без $wpdb->prepare(). Он не может определить, проверяет ли обработчик нужное право, подходит ли функция экранирования к контексту вывода, уходит ли серверный запрос на URL, который контролирует пользователь, и может ли один пользователь добраться до данных другого. Кроме того, комментарий phpcs:ignore может заглушить снифф. Мы рассматриваем WPCS как нижнюю планку, которая не пропускает известные ошибки, а изменения, важные для безопасности, проверяем вручную.

Как PHPStan работает с функциями ядра WordPress, не выполняя код?

PHPStan никогда не запускает анализируемый код. Он читает объявления и докблоки, чтобы построить модель типов. Для WordPress szepeviktor/phpstan-wordpress поставляет файлы-стабы: сигнатуры, типы параметров и возвращаемые типы функций и классов ядра WordPress без их тел. PHPStan читает эти стабы так, будто это WordPress, поэтому может проверить, что вызов wp_delete_file() или get_attached_file() передает и получает правильные типы. Константы, которые WordPress или плагин определяют во время выполнения, помещаются в bootstrap-файл, который PHPStan загружает перед анализом.

Плагин опубликован на WordPress.org как smallPict. Установка и настройки «Выходной формат» описаны в руководстве по установке и справочнике по настройкам изображений.

Автор: smallPict Engineering

Engineering

Мы делаем конвертацию изображений и доставку через CDN для сайтов и приложений.

Попробуйте на своих изображениях.

20 МБ в месяц бесплатно. Около двух минут на настройку в WordPress.

Ещё о производительности изображений, Core Web Vitals и облачном хранилище.

Все статьи

Web Performance4 min read

Почему современным сайтам на WordPress нужна двойная доставка AVIF/WebP (и как её настроить, не ломая браузеры)

Доставляйте AVIF с резервным WebP через теги HTML5 <picture>, чтобы уменьшить самые тяжелые изображения и улучшить LCP на мобильных устройствах, не ломая старые браузеры и не расходуя облачную квоту на каждую миниатюру.

Cloud & CDN3 min read

SmallPict API/SDK: собственный CDN (BYOK) или управляемый CDN — что выбрать?

На тарифе Ignite CDN нет (результаты возвращаются прямо в приложение), а на Velocity и Momentum можно выбрать хранилище и CDN SmallPict (по умолчанию) или собственные хранилище и CDN. Рассказываем, как понять, какая модель подходит вашему проекту.

WordPress2 min read

Оптимизация изображений WordPress с CDN: что входит в тариф и что считается добросовестным использованием

Платные тарифы SmallPict для WordPress включают быструю локальную доставку (Starter) или необязательный управляемый глобальный CDN (Pro и Business). Рассказываем, что именно входит в тариф, что считается добросовестным использованием и как работает наш общий пул использования для нескольких сайтов.