レガシーPHPリファクタ 20:GitHub ActionsによるCIの構築

2007年に執筆した『Smarty動的Webサイト構築入門』のサンプルコードリポジトリでは、レガシーSmartyやPHPを現代の基準でどのようにリファクタリングしていけばよいのか、その具体的な実践例を示すためのメンテナンスをしています。

本書のサンプルコードは、どこかの本番環境へデプロイ・リリースされるものではありません。だからこそ、「レガシーなコードをどうモダンな開発プラクティスに乗せ換えていくか」という移行のプロセスやベストプラクティスを、ありのままに見せるための格好の実験場となっています。

レガシーなシステムを引き継いでしまったエンジニアが最も恐れるのは、「ちょっとした修正がどこに波及するか分からない」「手動での確認漏れで本番が壊れる」という恐怖です。この恐怖を払拭し、リファクタリングを加速させるために導入したのが、GitHub ActionsによるCI(継続的インテグレーション)環境です。

今回は、マルチ言語・多層防御を実現した実際の GitHub Actions(コンポジットアクション)の構成と、その設計の裏側をご紹介します。


1. 変更のあったファイルだけを狙い撃つ:dorny/paths-filter の活用

レガシーな大規模リポジトリでは、あらゆる変更に対してすべての静的解析やテストをフルで回していると、CIの実行時間が長くなり、開発のテンポが落ちてしまいます。

そこで、.github/actions/lint/action.yml の先頭では dorny/paths-filter を使用し、追加・変更されたファイルの拡張子やパスに応じて、必要なチェックだけを選択的に実行するようにしています。

# ========================================
# ファイル変更検出系
# ========================================
- name: Detect changed files
  uses: dorny/paths-filter@v4
  id: paths_filter
  with:
    list-files: shell
    filters: |
      sh:
        - added|modified: '**/*.{sh,sh.tmpl}'
      docker:
        - added|modified: 'docker/**/Dockerfile'
      js:
        - added|modified: 'smartybook/**/*.{js,mjs,ts,tsx}'
      php:
        - added|modified: 'app/**/*.php'
        - added|modified: 'bootstrap/**/*.php'
        - added|modified: 'smartybook/**/*.php'
        - added|modified: 'tests/**/*.php'
      github_yaml:
        - added|modified: '.github/**/*.{yml,yaml}'
      prettier:
        - added|modified: '**/*.{css,yml,yaml,md,json,jsonc}'

これにより、例えば PHP のファイルを1つ直したときに、無関係な JS や Docker の静的解析が走って待ち時間が増えるのを防いでいます。


2. 多言語・多層防御のリンター&静的解析スタック

レガシーコードの近代化では、コードの「見た目(フォーマット)」から「品質(静的解析)」まで、機械的に強制する仕組みが不可欠です。本リポジトリでは、以下のツール群を GitHub Actions 上で完全自動化しています。

シェル・コンテナ・ワークフロー系

  • ShellCheck: シェルスクリプトのバグやアンチパターンを検出
  • Hadolint: Dockerfile のベストプラクティス違反をチェック
  • actionlint: GitHub Actions のワークフロー定義ミスの検出

JS / フロントエンド系

  • Prettier (JS/CSS/YAML/MD/JSON 等): コードフォーマットの統一。なお、自動生成物である pnpm-lock.yaml は整形規則が衝突しやすいため .prettierignore で除外しています。
  • ESLint: JavaScript のコード品質・作法チェック
  • ts-check: TypeScript による型チェック(JSDocを通した緩やかな型安全性の担保も含む)

PHP / Smarty 系

  • PHP 構文チェック (php -l): 最速でパースエラーを弾く最初の砦
  • php-cs-fixer: コーディング規約の自動チェック・強制
  • SmartyLint: レガシーなテンプレート構文のエラーやミスを検知
  • PHPStan: 型やメソッド呼び出しの静的解析(レガシーPHPの「存在しないメソッド呼び出し」などの致命傷をコンパイルタイムならぬCIタイムで防ぐ)

3. 実際のコンポジットアクション定義

これらの一連のチェックをまとめた .github/actions/lint/action.yml の全体像は以下の通りです。

name: lint
description: Lint files (format, lint, static analysis)
runs:
  using: composite
  steps:
    - name: Detect changed files
      uses: dorny/paths-filter@v4
      id: paths_filter
      with:
        list-files: shell
        filters: |
          sh:
            - added|modified: '**/*.{sh,sh.tmpl}'
          docker:
            - added|modified: 'docker/**/Dockerfile'
          js:
            - added|modified: 'smartybook/**/*.{js,mjs,ts,tsx}'
          php:
            - added|modified: 'app/**/*.php'
            - added|modified: 'bootstrap/**/*.php'
            - added|modified: 'smartybook/**/*.php'
            - added|modified: 'tests/**/*.php'
          github_yaml:
            - added|modified: '.github/**/*.{yml,yaml}'
          prettier:
            - added|modified: '**/*.{css,yml,yaml,md,json,jsonc}'

    - name: Run ShellCheck
      if: steps.paths_filter.outputs.sh == 'true'
      shell: bash
      run: shellcheck ${{ steps.paths_filter.outputs.sh_files }}

    - name: Run Hadolint
      if: steps.paths_filter.outputs.docker == 'true'
      uses: hadolint/hadolint-action@v3.5.0
      with:
        dockerfile: ${{ steps.paths_filter.outputs.docker_files }}

    - name: Check format (Prettier)
      if: steps.paths_filter.outputs.prettier == 'true'
      shell: bash
      run: pnpm run format:check ${{ steps.paths_filter.outputs.prettier_files }}

    - name: Run actionlint
      if: steps.paths_filter.outputs.github_yaml == 'true'
      uses: reviewdog/action-actionlint@v1
      with:
        fail_level: warning
        reporter: github-pr-annotations

    - name: JS Check formatting
      if: ${{ steps.paths_filter.outputs.js == 'true' }}
      shell: bash
      run: pnpm run format:check ${{ steps.paths_filter.outputs.js_files }}

    - name: ESLint
      if: ${{ steps.paths_filter.outputs.js == 'true' }}
      shell: bash
      run: pnpm run lint ${{ steps.paths_filter.outputs.js_files }}

    - name: ts-check
      if: ${{ steps.paths_filter.outputs.js == 'true' }}
      shell: bash
      run: pnpm run ts-check

    - name: PHP syntax check
      shell: bash
      if: ${{ steps.paths_filter.outputs.php == 'true' }}
      run: php -l ${{ steps.paths_filter.outputs.php_files }}

    - name: Run php-cs-fixer
      if: ${{ steps.paths_filter.outputs.php == 'true' }}
      shell: bash
      run: composer run-script format:check ${{ steps.paths_filter.outputs.php_files }}

    - name: Run smarty-lint
      shell: bash
      run: composer run-script smarty-lint templates

    - name: phpstan
      shell: bash
      run: composer run-script lint

4. 常に走るテスト系と自動更新(Renovate等)の連携

上記の静検・Lintに加え、テスト系として以下が常に実行されます。

  • Pest 単体ユニットテスト
  • Docker環境を構築しての Golden Master テスト(※これについては別記事で詳しく解説予定です)

このCI基盤があるおかげで、RenovateやDependabotによるライブラリの自動更新PRであっても、テストと静的解析をすべてパスすれば人間がレビューしなくても安心して自動マージするといった運用が可能になります。


まとめ:めったに触らないシステムだからこそ、AIの力を借りてCI/CDを組もう

こうしたレガシーシステムは「めったに触らない(だからこそ手順や知見がチームにたまらない)」というケースがほとんどです。 たまのメンテナンスでデプロイや環境構築をしようとして、古い手順書と格闘した経験がある方も多いと思います。

  • めったに触らないからこそ、ビルドやテスト、静的解析の手順はすべて GitHub Actions などの CI/CD にコードとして閉じ込め、人間が忘れても機械が覚えていてくれる状態にする。
  • 知見がたまりにくいなら、AIにしつこいくらい「一般的な方法」「標準的な書き方」を質問しながら、一歩ずつ CI/CD の設定ファイルを組み立てていく。

CI/CD の構築や、各種リンター・テストの導入は、かんたんではありませんが、AIの力を借りながら、CI/CDを構築してみましょう。

参考コード

smartybook/docker-compose.yml at main · ninton/smartybook
Smarty動的Webサイト構築入門サンプルソース. Contribute to ninton/smartybook development by creating an account on GitHub.
レガシーPHPリファクタ 20:GitHub Actionsによる全自動CI CDの構築
Smarty動的Webサイト構築入門サンプルソース. Contribute to ninton/smartybook development by creating an account on GitHub.
タイトルとURLをコピーしました