😽

NativePHPでカスタムPHPバイナリをビルド

に公開

はじめに

NativePHPは、PHPとLaravelのスキルを活かしてデスクトップアプリケーションを構築できるフレームワークです。内部的には静的にリンクされたPHPバイナリを使用しており、このバイナリをカスタマイズすることで、デフォルトでは含まれていない拡張機能を追加できます。

本記事では、NativePHP用のカスタムPHPバイナリをビルドする方法を、実際に遭遇したトラブルとその解決策を交えながら解説します。

NativePHPとデータベース接続について

NativePHPの公式ドキュメントでは、データベースアクセスについて以下のように記載されています:

When building a NativePHP mobile application, you can't guarantee a safe, secure, reliable environment (if it's being distributed publicly). So you should have a layer of abstraction in-between your application and the database server. When working with Laravel, this would usually be a REST API.

— NativePHP Databases Documentation

(訳:NativePHPモバイルアプリケーションを構築する際、公開配布される場合は安全で信頼性の高い環境を保証できません。そのため、アプリケーションとデータベースサーバーの間に抽象化レイヤーを設けるべきです。Laravelの場合、これは通常REST APIになります。)

これはセキュリティ上の観点から、直接MySQL/PostgreSQLに接続するのではなく、API経由でのアクセスを推奨するものです。公開配布するアプリでは、データベース認証情報が漏洩するリスクや、任意のSQLクエリが実行される危険性があるためです。

しかし、私のケースでは現地でローカルネットワークを構築し、インターネットに公開しないクローズドな環境で運用するアプリケーションを開発していました。複数のPCにインストールして同時に動作させる必要があり、さらにNativePHP以外のツールも同じデータベースにアクセスする要件がありました。

このような閉域網環境では、専用のAPIサーバーを別途用意するよりも、各アプリケーションから直接データベースにアクセスする方が運用上合理的です。そのため、mysqli、pgsqlなどのデータベース拡張機能を含むカスタムバイナリが必要になりました。

なぜカスタムバイナリが必要?

NativePHPの公式php-binリポジトリには、一般的な拡張機能が含まれていますが、以下のようなケースではカスタムビルドが必要になります:

  • PostgreSQL対応: pgsql、pdo_pgsql拡張機能の追加
  • SQL Server対応: sqlsrv、pdo_sqlsrv拡張機能の追加
  • 高速圧縮: zstd、brotliライブラリの追加
  • HTTP/2対応: nghttp2ライブラリの追加

プロジェクト構造

ディレクトリ構成

php-bin/
├── .github/
│   └── workflows/
│       ├── build-php.yml        # PHPビルドワークフロー
│       └── update-ca-file.yml   # CA証明書更新ワークフロー
├── cacert.pem                   # SSL/TLS証明書バンドル
├── php-extensions.txt           # コンパイル対象の拡張機能
└── php-libraries.txt            # コンパイル対象のライブラリ

主要ファイルの役割

ファイル 役割
php-extensions.txt ビルドする拡張機能をカンマ区切りで指定
php-libraries.txt 追加するライブラリをカンマ区切りで指定
cacert.pem HTTPS通信用のCA証明書バンドル

static-php-cliとは

static-php-cliは、静的にリンクされたPHPバイナリを構築するためのツールです。

特徴

  • 自己完結型: 外部ライブラリ依存なしで実行可能
  • クロスプラットフォーム: Linux、macOS、Windowsに対応
  • 170以上の拡張機能: 幅広い拡張機能をサポート

サポートされているプラットフォーム

OS x86_64 arm64
Linux ✓ ✓
macOS ✓ ✓
Windows ✓ ✗

サポートされているPHPバージョン

  • PHP 8.1, 8.2, 8.3, 8.4

拡張機能とライブラリの設定

拡張機能の設定(php-extensions.txt)

bcmath,bz2,calendar,ctype,curl,dom,exif,fileinfo,filter,ftp,gd,iconv,
mbregex,mbstring,mysqli,mysqlnd,opcache,openssl,pdo,pdo_mysql,pdo_pgsql,
pdo_sqlite,pgsql,phar,redis,session,simplexml,soap,sockets,sqlite3,
tokenizer,xml,xmlreader,xmlwriter,zip,zlib,intl,sqlsrv,ssh2,pdo_sqlsrv

ライブラリの設定(php-libraries.txt)

libjpeg,freetype,libwebp,zstd,brotli,nghttp2

拡張機能とライブラリの依存関係

static-php-cliでは、拡張機能には必須とオプションの依存関係があります。

拡張機能 必須ライブラリ オプションライブラリ
gd zlib, libpng libjpeg, freetype, libwebp, libavif
curl openssl, zlib libssh2, brotli, nghttp2, zstd
redis session igbinary, msgpack, zstd, liblz4
intl icu -

オプションライブラリは --with-libs オプションで明示的に指定する必要があります。

GitHub Actionsでのビルド設定

ワークフローの全体構成

name: Build PHP

permissions:
  contents: write
  pull-requests: write

on:
  workflow_dispatch:

env:
  SPC_VERSION: 2.6.1

jobs:
  build:
    name: ${{ matrix.version }} ${{ matrix.os }}
    runs-on: ${{ matrix.os }}

    strategy:
      max-parallel: 5  # APIレート制限対策
      matrix:
        version: ["8.1", "8.2", "8.3", "8.4"]
        os: ["macos-15-intel", "macos-latest", "ubuntu-latest", "ubuntu-24.04-arm", "windows-latest"]

    continue-on-error: true

プラットフォーム別のSPCバイナリ設定

- name: Set SPC URL for macos-15-intel
  if: matrix.os == 'macos-15-intel'
  run: echo "SPC_URL=https://github.com/crazywhalecc/static-php-cli/releases/download/${{ env.SPC_VERSION }}/spc-macos-x86_64.tar.gz" >> $GITHUB_ENV

- name: Set SPC URL for macos-latest
  if: matrix.os == 'macos-latest'
  run: echo "SPC_URL=https://github.com/crazywhalecc/static-php-cli/releases/download/${{ env.SPC_VERSION }}/spc-macos-aarch64.tar.gz" >> $GITHUB_ENV

- name: Set SPC URL for ubuntu-latest
  if: matrix.os == 'ubuntu-latest'
  run: echo "SPC_URL=https://github.com/crazywhalecc/static-php-cli/releases/download/${{ env.SPC_VERSION }}/spc-linux-x86_64.tar.gz" >> $GITHUB_ENV

- name: Set SPC URL for ubuntu-24.04-arm
  if: matrix.os == 'ubuntu-24.04-arm'
  run: echo "SPC_URL=https://github.com/crazywhalecc/static-php-cli/releases/download/${{ env.SPC_VERSION }}/spc-linux-aarch64.tar.gz" >> $GITHUB_ENV

- name: Set SPC URL for windows-latest
  if: matrix.os == 'windows-latest'
  run: |
    echo "SPC_URL=https://github.com/crazywhalecc/static-php-cli/releases/download/${{ env.SPC_VERSION }}/spc-windows-x64.exe" >> $GITHUB_ENV
    echo "SPC_BINARY=spc.exe" >> $GITHUB_ENV

ビルドコマンド

- name: Build PHP
  run: |
    cd ../static-php-cli
    ./bin/spc build --build-cli "${{ env.PHP_EXTENSIONS }}" --with-libs="${{ env.PHP_LIBS }}" --debug

トラブルシューティング

問題1: macOS-13ランナーの廃止

症状

The macOS-13 based runner images are now retired.

原因

GitHub Actionsのmacos-13ランナーが2025年12月に廃止されました。

解決策

macos-13をmacos-15-intelに変更します。

# 変更前
os: ["macos-13", "macos-latest", ...]

# 変更後
os: ["macos-15-intel", "macos-latest", ...]

問題2: Windowsでライブラリが未サポート

症状

[C] library [brotli] is in the lib.json list but not supported to compile
[C] library [zstd] is in the lib.json list but not supported to compile

原因

static-php-cliの一部ライブラリはWindowsでサポートされていません。

ライブラリ Linux macOS Windows
zstd ✓ ✓ ✗
brotli ✓ ✓ ✗
nghttp2 ✓ ✓ ✗

解決策

Windowsビルド時のみ、未サポートライブラリを除外します。

- name: Read PHP libraries from file
  shell: bash
  run: |
    LIBRARIES=$(php -r "echo trim(file_get_contents('php-libraries.txt'));")
    # Windowsではzstd, brotli, nghttp2を除外(未サポート)
    if [[ "${{ matrix.os }}" == "windows-latest" ]]; then
      LIBRARIES=$(echo "$LIBRARIES" | sed 's/,zstd//g' | sed 's/,brotli//g' | sed 's/,nghttp2//g')
    fi
    echo "PHP_LIBS=$LIBRARIES" >> $GITHUB_ENV

問題3: GitHub APIレート制限

症状

curl: (22) The requested URL returned error: 403
Failed to fetch "https://api.github.com/repos/static-php/static-php-cli-hosted/releases"

原因

複数のジョブが同時にGitHub APIにアクセスすると、レート制限に達することがあります。

解決策

max-parallelを設定して同時実行数を制限します。

strategy:
  max-parallel: 5  # 同時実行数を5に制限
  matrix:
    version: ["8.1", "8.2", "8.3", "8.4"]
    os: ["macos-15-intel", "macos-latest", "ubuntu-latest", "ubuntu-24.04-arm", "windows-latest"]

max-parallelの動作

設定 動作
指定なし 全20ジョブを同時実行
max-parallel: 5 最大5ジョブまで同時実行、残りは待機
max-parallel: 1 1つずつ順番に実行(直列)

問題4: Windowsでintl拡張機能のビルド失敗

症状

error C2270: 'length': modifiers not allowed on nonmember functions
(compiling source file ext\intl\intl_convertcpp.cpp)

原因

static-php-cliで使用されるICUライブラリとWindowsのコンパイラ間で互換性問題が発生します。

解決策

Windowsビルド時のみ、intl拡張機能を除外します。

- name: Read PHP extensions from file
  shell: bash
  run: |
    EXTENSIONS=$(php -r "echo trim(file_get_contents('php-extensions.txt'));")
    # Windowsではintlを除外(ICU互換性問題)
    if [[ "${{ matrix.os }}" == "windows-latest" ]]; then
      EXTENSIONS=$(echo "$EXTENSIONS" | sed 's/,intl//g')
    fi
    echo "PHP_EXTENSIONS=$EXTENSIONS" >> $GITHUB_ENV

ワークフロー完全版

以下は、プラットフォーム別の除外処理を含む最終的なワークフロー全体です。

name: Build PHP

permissions:
  contents: write
  pull-requests: write

on:
  workflow_dispatch:

env:
  SPC_VERSION: 2.6.1
  # Temporary workaround. See https://github.com/NativePHP/laravel/issues/522#issuecomment-2736250539
  # Remove when updating SPC version
  WINBUILD_ACKNOWLEDGE_DEPRECATED: yes

jobs:
  build:
    name: ${{ matrix.version }} ${{ matrix.os }}
    runs-on: ${{ matrix.os }}

    strategy:
      max-parallel: 5  # APIレート制限対策:同時実行数を制限
      matrix:
        version: ["8.1", "8.2", "8.3", "8.4"]
        os: ["macos-15-intel", "macos-latest", "ubuntu-latest", "ubuntu-24.04-arm", "windows-latest"]

    continue-on-error: true

    steps:
      - uses: actions/checkout@v4

      - name: Set SPC binary name
        shell: bash
        run: echo "SPC_BINARY=spc" >> $GITHUB_ENV

      - name: Set SPC URL for macos-15-intel
        shell: bash
        if: matrix.os == 'macos-15-intel'
        run: echo "SPC_URL=https://github.com/crazywhalecc/static-php-cli/releases/download/${{ env.SPC_VERSION }}/spc-macos-x86_64.tar.gz" >> $GITHUB_ENV

      - name: Set SPC URL for macos-latest
        shell: bash
        if: matrix.os == 'macos-latest'
        run: echo "SPC_URL=https://github.com/crazywhalecc/static-php-cli/releases/download/${{ env.SPC_VERSION }}/spc-macos-aarch64.tar.gz" >> $GITHUB_ENV

      - name: Set SPC URL for ubuntu-latest and ubuntu-24.04
        shell: bash
        if: matrix.os == 'ubuntu-latest'
        run: echo "SPC_URL=https://github.com/crazywhalecc/static-php-cli/releases/download/${{ env.SPC_VERSION }}/spc-linux-x86_64.tar.gz" >> $GITHUB_ENV

      - name: Set SPC URL for ubuntu-24.04-arm
        shell: bash
        if: matrix.os == 'ubuntu-24.04-arm'
        run: echo "SPC_URL=https://github.com/crazywhalecc/static-php-cli/releases/download/${{ env.SPC_VERSION }}/spc-linux-aarch64.tar.gz" >> $GITHUB_ENV

      - name: Set SPC URL for windows-latest
        shell: bash
        if: matrix.os == 'windows-latest'
        run: |
          echo "SPC_URL=https://github.com/crazywhalecc/static-php-cli/releases/download/${{ env.SPC_VERSION }}/spc-windows-x64.exe" >> $GITHUB_ENV
          echo "SPC_BINARY=spc.exe" >> $GITHUB_ENV

      - name: Download SPC
        shell: bash
        run: |
          cd ..
          if [[ "${{ matrix.os }}" == "windows-latest" ]]; then
            curl -fsSL -o ${{ env.SPC_BINARY }} ${{ env.SPC_URL }}
          else
            curl -fsSL -o ${{ env.SPC_BINARY }}.tar.gz ${{ env.SPC_URL }}
            tar -xzf ${{ env.SPC_BINARY }}.tar.gz
          fi
          chmod +x ${{ env.SPC_BINARY }}
          [ ! -d static-php-cli/bin ] && mkdir -p static-php-cli/bin
          mv ${{ env.SPC_BINARY }} static-php-cli/bin/

      - name: Create php-bin directory
        shell: bash
        run: |
          cd ..
          [ ! -d php-bin ] && mkdir -p php-bin
          cd php-bin

      - shell: bash
        run: |
          PHP_VERSION=$(echo "${{ matrix.version }}" | cut -d. -f1,2)
          echo "PHP_VERSION=$PHP_VERSION" >> $GITHUB_ENV

      - shell: bash
        run: echo "SPC_BUILD_ARCH=x64" >> $GITHUB_ENV

      - shell: bash
        if: matrix.os == 'macos-latest' || matrix.os == 'ubuntu-24.04-arm'
        run: echo "SPC_BUILD_ARCH=arm64" >> $GITHUB_ENV

      - shell: bash
        if: contains(matrix.os, 'macos')
        run: |
          brew install automake gzip
          echo "SPC_BUILD_OS=mac" >> $GITHUB_ENV

      - shell: bash
        if: matrix.os == 'windows-latest'
        run: echo "SPC_BUILD_OS=win" >> $GITHUB_ENV

      - shell: bash
        if: matrix.os == 'ubuntu-latest' || matrix.os == 'ubuntu-24.04-arm'
        run: echo "SPC_BUILD_OS=linux" >> $GITHUB_ENV

      - name: Setup system PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: 8.3
          tools: pecl, composer
          extensions: curl, openssl, mbstring, sodium, tokenizer
          ini-values: memory_limit=-1

      - name: SPC doctor
        run: |
          cd ../static-php-cli
          ./bin/${{ env.SPC_BINARY }} doctor
          cd ../php-bin

      - name: Read PHP extensions from file
        id: read-extensions
        shell: bash
        run: |
          EXTENSIONS=$(php -r "echo trim(file_get_contents('php-extensions.txt'));")
          # Windowsではintlを除外(ICU互換性問題)
          if [[ "${{ matrix.os }}" == "windows-latest" ]]; then
            EXTENSIONS=$(echo "$EXTENSIONS" | sed 's/,intl//g')
          fi
          EXT_HASH=$(php -r "echo md5(getenv('EXTENSIONS'));")
          echo "PHP_EXTENSIONS=$EXTENSIONS" >> $GITHUB_ENV
          echo "PHP_EXT_HASH=$EXT_HASH" >> $GITHUB_ENV

      - name: Read PHP libraries from file
        id: read-libs
        shell: bash
        run: |
          LIBRARIES=$(php -r "echo trim(file_get_contents('php-libraries.txt'));")
          # Windowsではzstd, brotli, nghttp2を除外(未サポート)
          if [[ "${{ matrix.os }}" == "windows-latest" ]]; then
            LIBRARIES=$(echo "$LIBRARIES" | sed 's/,zstd//g' | sed 's/,brotli//g' | sed 's/,nghttp2//g')
          fi
          LIBRARIES_HASH=$(php -r "echo md5(getenv('LIBRARIES'));")
          echo "PHP_LIBS=$LIBRARIES" >> $GITHUB_ENV

      # Cache downloaded source
      - id: cache-spc-downloads
        uses: actions/cache@v4
        with:
          path: ../static-php-cli/downloads
          key: spc-downloads-${{ env.PHP_EXT_HASH }}

      - name: Download PHP extension sources
        if: steps.cache-spc-downloads.outputs.cache-hit != 'true'
        run: |
          cd ../static-php-cli
          ./bin/${{ env.SPC_BINARY }} download --with-php=${{ matrix.version }} --for-extensions "${{ env.PHP_EXTENSIONS }}" --prefer-pre-built
          cd ../php-bin

      - name: Build PHP
        run: |
          cd ../static-php-cli
          ./bin/${{ env.SPC_BINARY }} build --build-cli "${{ env.PHP_EXTENSIONS }}" --with-libs="${{ env.PHP_LIBS }}" --debug
          cd ../php-bin

      - name: Get built PHP version
        shell: bash
        run: |
          if [[ "${{ matrix.os }}" == "windows-latest" ]]; then
            PHP_VERSION_FULL=$(../static-php-cli/buildroot/bin/php.exe -r "echo PHP_VERSION;")
          else
            PHP_VERSION_FULL=$(../static-php-cli/buildroot/bin/php -r "echo PHP_VERSION;")
          fi
          echo "PHP_VERSION_FULL=$PHP_VERSION_FULL" >> $GITHUB_ENV

      - name: Create bin directories
        shell: bash
        run: |
          mkdir -p bin/${{ env.SPC_BUILD_OS }}/${{ env.SPC_BUILD_ARCH }}
          mkdir -p license-files
          mkdir -p build-meta

      - name: Zip PHP binary, copy metadata
        shell: bash
        run: |
          if [[ "${{ matrix.os }}" == "windows-latest" ]]; then
            powershell Compress-Archive -Path "../static-php-cli/buildroot/bin/php.exe" -DestinationPath "bin/${{ env.SPC_BUILD_OS }}/${{ env.SPC_BUILD_ARCH }}/php-${{ env.PHP_VERSION }}.zip" -Force
          else
            rm -f bin/${{ env.SPC_BUILD_OS }}/${{ env.SPC_BUILD_ARCH }}/php-${{ env.PHP_VERSION }}.zip
            mkdir -p tmp-bin
            cp ../static-php-cli/buildroot/bin/php tmp-bin/
            cd tmp-bin
            zip ../bin/${{ env.SPC_BUILD_OS }}/${{ env.SPC_BUILD_ARCH }}/php-${{ env.PHP_VERSION }}.zip php
            cd ..
            rm -rf tmp-bin
          fi
          cp ../static-php-cli/buildroot/license/* license-files/
          cp ../static-php-cli/buildroot/build-extensions.json build-meta/build-extensions-${{ env.SPC_BUILD_OS }}.json
          cp ../static-php-cli/buildroot/build-libraries.json build-meta/build-libraries-${{ env.SPC_BUILD_OS }}.json

      - name: Create Pull Request
        uses: peter-evans/create-pull-request@v7
        with:
          branch: update-php-${{ env.PHP_VERSION }}-${{ env.SPC_BUILD_OS }}-${{ env.SPC_BUILD_ARCH }}
          title: "Update PHP ${{ env.PHP_VERSION }} build for ${{ env.SPC_BUILD_OS }} ${{ env.SPC_BUILD_ARCH }}"
          commit-message: "build"
          body: |
            PHP: ${{ env.PHP_VERSION_FULL }}
            Exts: ${{ env.PHP_EXTENSIONS }}
            OS: ${{ env.SPC_BUILD_OS }}
            Arch: ${{ env.SPC_BUILD_ARCH }}
          base: main

cacert.pemの役割と取得方法

静的リンクされたPHPはOSのCA証明書ストアにアクセスできないため、HTTPS通信用の証明書をバンドルする必要があります。

用途

  • PHPのcurl拡張機能でのHTTPS接続
  • file_get_contents('https://...')でのアクセス
  • NativePHPアプリでのAPI通信

取得方法

CA証明書はcurl公式サイトから取得できます。

# 最新のCA証明書をダウンロード
curl -O https://curl.se/ca/cacert.pem

自動更新ワークフロー

定期的に最新の証明書を取得するワークフローを設定します。

name: Fetch Latest CA File

on:
  workflow_dispatch:
  schedule:
    - cron: '0 0 * * 0'  # 毎週日曜日に実行

jobs:
  fetch-ca-file:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4

    - name: Fetch the latest CA file
      run: curl -O https://curl.se/ca/cacert.pem

    - name: Check if the CA file has changed
      id: cafile
      run: |
        CHANGED=$(git diff --name-only | grep cacert.pem || true)
        if [[ ! -z "$CHANGED" ]]; then
          echo "CHANGED=true" >> $GITHUB_ENV
        fi

    - name: Commit changes
      if: env.CHANGED == 'true'
      run: |
        git config --local user.email "your-email@example.com"
        git config --local user.name "Your Name"
        git add cacert.pem
        git commit -m "Update CA file"
        git push

PHPでの使用例

NativePHPアプリ内でHTTPS通信を行う場合:

// curlでの使用
$ch = curl_init('https://api.example.com');
curl_setopt($ch, CURLOPT_CAINFO, '/path/to/cacert.pem');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);

// stream_contextでの使用
$context = stream_context_create([
    'ssl' => [
        'cafile' => '/path/to/cacert.pem',
        'verify_peer' => true,
    ]
]);
$content = file_get_contents('https://api.example.com', false, $context);

NativePHPでカスタムバイナリを使用する

設定方法

.envファイルで環境変数を設定します。

NATIVEPHP_PHP_BINARY_PATH=/path/to/your-custom-php-bin/bin/

ディレクトリ構造

カスタムバイナリは以下の構造に従う必要があります:

bin/
├── linux/
│   ├── x64/
│   │   └── php-8.4.zip
│   └── arm64/
│       └── php-8.4.zip
├── mac/
│   ├── x64/
│   │   └── php-8.4.zip
│   └── arm64/
│       └── php-8.4.zip
└── win/
    └── x64/
        └── php-8.4.zip

まとめ

NativePHPでカスタムPHPバイナリをビルドする際の主なポイント:

  1. static-php-cliを使用して静的リンクされたPHPをビルド
  2. 拡張機能はphp-extensions.txt、ライブラリはphp-libraries.txtで管理
  3. プラットフォーム別の制約に注意(特にWindows)
  4. GitHub Actionsのレート制限対策としてmax-parallelを設定
  5. CA証明書はHTTPS通信のために必須(curl.seから取得)

参考リンク

Discussion