PHP-Watcher: инструмент, который упрощает разработку долгоживущих приложений



    Мы любим PHP за простоту: ты пишешь код, обновляешь страницу в браузере и сразу видишь изменения. Но если дело доходит до консольных команд, которые могут быть долгоживущими процессами, — например, если мы пишем асинхронный HTTP-сервер для загрузки файлов, — разработка может оказаться весьма болезненной.

    В экосистеме PHP не было подходящего решения, чтобы автоматически перезапускать приложения при изменениях в исходном коде. Поэтому я решил сделать свой инструмент — на чистом PHP и с доступом через Composer.

    Это как Nodemon, но на PHP


    Некоторое время я сам использовал Nodemon. Это инструмент из мира Node.js, однако если его немного поднастроить, можно использовать и с PHP-скриптами. Но ведь на самом деле я не хочу устанавливать Node.js и тащить кучу неизвестных мне NPM-пакетов в своё асинхронное PHP-приложение, чтобы перезапускать его.

    Поскольку я активно общаюсь в твиттере, то спросил там, кто еще сталкивался с такой проблемой и хотел бы получить решение. Увидев интерес, сел писать инструмент, который предоставляет такой же функционал, что и Nodemon, только на PHP и для PHP.



    Прошел месяц: с PHP-Watcher вам больше не нужно устанавливать Nodemon или любой другой пакет NPM для разработки вашего долгоживущего PHP-приложения.

    Вот так это работает


    Библиотеку можно установить через Composer:

    composer global require seregazhuk/php-watcher

    Представим, что мы работаем над долгоживущим приложением на основе Symfony. Точка входа в наше приложение — файл public/index.php. Мы хотим отслеживать изменения в папках src и config. Еще мы хотим, чтобы приложение автоматически перезапускалось, как только мы изменим исходный код или параметры конфигурации. Вот как можно решить эту задачу:

    php-watcher public/index.php --watch src --watch config

    Команда запустит скрипт public/index.php, который начнёт отслеживать изменения в директориях src и config. Как только в любой из них изменится файл, PHP-Watcher перезапустит скрипт.

    По умолчанию он отслеживает изменения только в PHP-файлах. Но Symfony хранит свои конфиги в yaml. Поэтому нам нужно явно указать “вотчеру”, чтобы он отслеживал как PHP, так и yaml-файлы. Делается это с помощью опции --ext:

    php-watcher public/index.php --watch src --watch config --ext php,yaml

    Допустим, мы поняли, что нам не нужно перезапускать приложения при любых изменениях внутри директории src. Например, нам бы хотелось игнорировать изменения в поддиректории src/Migrations. В этом случае можно воспользоваться опцией --ignore:

    php-watcher public/index.php --watch src --watch config --ext php,yaml --ignore Migrations

    Теперь PHP-Watcher начнёт отслеживать изменения в директориях src и config, но будет игнорировать любые изменения внутри поддиректории Migrations. Кроме того, он по умолчанию игнорирует изменения во всех dot- и VCS-файлах.

    “Вотчер” поддерживает настройку своего поведения не только через опции командной строки, но и через файлы конфигурации. Если не хочется каждый раз в командной строке передавать кучу опций и параметров, то можно создать файл конфигурации .php-watcher.yml. Например, предыдущую команду можно заменить следующим конфигурационным файлом:

    watch:
      - src
      - config
    extensions:
      - php
      - yaml
    ignore:
      - Migrations
    

    Имея такой файл, мы можем просто активировать “вотчер”, указав лишь PHP-скрипт, который нужно перезапускать. Все остальные настройки будут взяты из файла:

    php-watcher public/index.php

    Что будет, если у нас есть и файл конфигурации и аргументы командной строки? В таком случае все аргументы, переданные из командной строки, перезатрут соответствующие значения из файла конфигурации.

    По умолчанию PHP-Watcher использует исполняемый файл PHP, чтобы запустить скрипт. Мы пишем в терминале команду:

    php-watcher public/index.php

    Под капотом создается дочерний процесс с командой php public/index.php. В большинстве случаев это то, что нужно. Однако если в вашем окружении иной исполняемый файл, можно явно указать, какую команду следует выполнить. Например, когда у нас есть несколько версий PHP в одной системе, а мы хотим запускать наше приложение с исполняемым файлом php7.4, можно воспользоваться опцией --exec и указать свой исполняемый файл:

    php-watcher public/index.php --exec php7.4

    То же самое через файл конфигурации:

    watch:
      - src
      - config
    executable: php7.4

    PHP-Watcher автоматически не перезапускает приложение, если оно упало. В dev-окружении в этом и нет особой необходимости — ведь пока мы разрабатываем новое приложение, это нормально, что иногда оно будет крашиться. Если приложение упало (завершилось с кодом, отличным от 0), “вотчер” даст нам об этом знать. Как только мы пофиксим код, изменения будут обнаружены в исходниках — и приложение перезапустится.

    Спасибо за внимание! Больше информации о PHP-Watcher можно найти на домашней странице проекта на GitHub. В документации описаны основные паттерны использования. Проект пока ещё находится в стадии разработки, но API уже довольно стабильный. Буду рад, если воспользуетесь.

    P.S. Не стесняйтесь оставлять отзывы и пожелания через issue на GitHub.
    Skyeng
    132,02
    Крутейшая edtech-команда страны. Удаленная работа
    Поделиться публикацией

    Комментарии 22

      –2
      pm2 в помощь
        +5
        Это же для NodeJs. Изначальная идея была — получить что-то на чистом PHP, что можно было бы добавить в проект через Composer.
          0

          pm2 работает со всеми скриптовыми ЯП

            +5
            Это понятно, но для его установки нужен NodeJs и npm. Не хочется стек PHP-приложения усложнять Node-зависимостями.
        0
        Спасибо большое, пока еще не успел посмотреть более детально, но судя по статье, это то, что уже давно было нужно.
        Жалко только, что о многопоточности пока речи не идет, умело бы оно в потоки, цены бы ему небыло.
          0

          Так это же PHP, здесь не будет многопоточности. А какой хотелось бы кейс, чтобы была многопоточность? У меня идея была просто рестартовать запущенное приложение при изменении его исходников.

            0
            Извиняюсь, сейчас глянул подробнее про инструмент, не совсем то, что я ожидал.
            Я все таки думал что он еще умеет жить как event-loop.
              +1

              Так он и живет как event-loop. А что Вы ожидали? Какое поведение?

                0
                А какой хотелось бы кейс, чтобы была многопоточность?
                Тогда не совсем понятен вопрос, что значит кейс в котором была бы многопоточность? Хотелось бы чтобы любой эвент уходил в поток.

                Увидел, код капотом ReactPHP, я все таки надеялся что это альтернатива, вопрос закрыт.
                0
                Вы не спутали его с roadrunner?
            0
            По документации похоже на supervisord.org
              +1
              Похоже наверно только тем, что рестартуется приложение. Здесь была идея слушать изменения в файлах и если они есть, то рестартовать. Кроме того, если слушаемое приложение упало (зафейлилось или просто выполнилось), то оно автоматически не рестартуется. Считаем, что это процесс разработки и здесь «нормально», если приложение упало. Как только будут внесены изменения в исходный код, watcher их обнаружит и перезапустит приложение.
              0

              Хотелось бы больше конкретики в плане описания принципов внутренней работы. Правильно я понимаю что вотчер следит за изменениями в дате модификации файла и каким-то образом производит рестарт уже запущенного процесса?

                +1

                Под капотом консольная команда запускает 2 дочерних процесса:


                • скрипт, который слушает изменения в файловой системе. Для этого используется Symfony Finder Component
                • самое приложение, которое нужно рестартовать

                Всё это управляется асинхронно на ReactPHP. Как только мы получаем сигнал от дочернего процесса, что файловая система была изменена, мы перезапускаем процесс с приложением.

                  0

                  Уточню, что для поиска изменений в файловой системе используется yosymfony/resource-watcher.

                0
                Внутри Symfony Finder используется SplFileInfo::getMTime()
                www.php.net/manual/ru/splfileinfo.getmtime.php
                это обертка над filemtime()
                Замечание: Результаты этой функции кешируются.

                Вопрос. Вы clearstatcache() вызываете?
                  0

                  Не вызываю. Извинюясь, я не совсем корректно описал принцип работы. Именно для поиска изменений на диске используется yosymfony/resource-watcher. А Symfony Finder используется лишь, чтобы задать критерию для поиска изменений.

                  0
                  А inotify + incron почему не устраивает?
                    0
                    www.php.net/manual/ru/book.inotify.php тем более что интерфейс в пыхе есть.
                      0

                      Хотелось решения на чистом PHP, чтобы можно было вызвать composer install и начать работать. С inotify мне нужно устанавливать дополнительные пакеты в систему.

                        +2
                        так это же пакет пыховский.
                        Вы же симфони целое сюда прикрутили, а одного пакета не хотите.

                        Просто дергать файловую систему вместо того чтобы ждать нужного события как бы экстенсивно. Это все равно вместо того чтобы использовать select/poll на сокетах пытаться все время из них что-то прочитать.

                        Тулза для мониторинга должна потреблять минимум ресурсов. А у Вас файловую систему зря грузит.
                          +1

                          Ага, посмотрел уже более детально. Спасибо за идею! Думаю можно сделать несколько реализаций. Проверять если доступно расширение — то использовать его, если нет — то фолбэк на Symfony и дергать файловую систему.

                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                    Самое читаемое