「PHP2Shell」のサービス終了ドミノと、Android SDKバージョンの厳格化は同じ構造の悩みではないか?

古いPHPバージョンを維持できずに、そのままサービスを終了せざるを得ないWebサービスやサーバーのニュースを耳にする機会が増えています。古いPHPにおける「PHP2Shell」などの脆弱性リスクやサポート終了に伴う対応の難しさは、IT業界の大きな頭痛の種です。

ふと思ったのですが、これってスマートフォンのAndroidアプリにおける「Android SDK(APIレベル)」のバージョン強制問題とまったく同じ構造ではないでしょうか?

今回は、この「レガシー基盤のジレンマ」について、共通点と背景を整理してみたいと思います。

 

🤔 なぜ「同じ」と言えるのか? 共通する3つの構造

表面上は「サーバー側の言語(PHP)」と「モバイルのOS環境(Android)」で領域が違いますが、本質的な課題は驚くほど似ています。

土台のライフサイクルと、容赦ない切り捨て

PHPの現実: 古いPHPバージョン(7系や初期の8系など)はすでに公式サポートが終了しています。重大な脆弱性が見つかってもパッチが当たらないため、安全性を保つには最新版へ上げるしかありません。しかし、古いWebアプリや自作のプラグインがそのバージョンに対応していません。

Androidの現実: Google Playストアのポリシーにより、アプリがターゲットとすべき「ターゲットAPIレベル(SDKのバージョン)」の最低要件が毎年引き上げられます。古いSDKのまま放置されたアプリは、新しいAndroid OSのセキュリティ基準を満たせず、ストアから削除されたりアップデートができなくなります。

「動いているのに、強制的に動かなくなる」ジレンマ

どちらのケースも、「プログラム自体は今のままで、エラーも出さずに元気に動いている」という状態であることが多くあります。
しかし、言語側やプラットフォーム側が「セキュリティ」や「エコ全体の健全化」を理由にアップデートを強制するため、現役で動いていたはずのシステムが突如として「NG」判定を受けることになります。

膨大な「移行・修正コスト」の壁

古いコードを最新のPHPや新しいAndroid SDKに追従させるには、非推奨(デプリケーティッド)になった関数の書き換えや、ライブラリの全面的な見直しが必要です。これには膨大な開発工数とコストがかかるため、リソースのない個人開発や中小サービスでは、「直すくらいならサービスを終了しよう」という苦渋の決断に至ってしまいます。

 

🤔 決定的な違いは「誰が強制しているか」

構造は似ていますが、外圧の性質には少し違いがあります。

Android SDK: Googleという「絶対的なプラットフォームホルダー」が、規約をもってトップダウンで強制してくる。

PHP2Shell / サーバー: プラットフォームからの強制というよりは、ネット上の「セキュリティの脅威(ボットによる攻撃や脆弱性)」というボトムアップ(環境起因)の現実が、サービスを安全に継続させることを不可能にさせる。

結果として「続けられなくなる」という結末は同じでも、そのプレッシャーの出所が異なります。

 

🤔 まとめ:ITエコシステムの高齢化という宿命

PHPのバージョン問題も、Android SDKの追従問題も、根っこにあるのは「ITエコシステムの高齢化と、それに伴う保守コストの肥大化」です。

ソフトウェアは一度作ったら終わりではなく、その周りを取り巻く環境(OSや言語、セキュリティ基準)の高速な進化に合わせて絶えずメンテナンスし続けなければなりません。

「動いているからそのままにしておきたい」という開発者側の願いと、「安全のために進化を強制する」というプラットフォームのルールの板挟み。このジレンマにどう向き合うかが、現代のシステム開発・運営における大きな課題だと言えそうです。


PHP2Shell で騒ぐ前に知っておくべきこと:「PHPのバージョンを上げれば終わり」ができない現場の構造的欠陥

最近、「PHP2Shell」と呼ばれる深刻な脆弱性が話題になり、SNSやセキュリティ関連のニュースで大きく取り上げられています。

これに対して「何をそんなに大騒ぎしているのか? PHPのバージョンを安全な版にアップデートすれば終わる話ではないのか?」と感じた人も多いのではないでしょうか。

結論から言えば、その通りです。

技術的な解決策としては「修正されたバージョンに上げる」のが唯一かつ完全な正解です。しかし、それでもこれほどの大騒ぎになっているのは、「バージョンを上げることすらできないレガシーシステムが、世の中に大量に放置されているから」に他なりません。

この記事では、なぜ「バージョンアップ」という当たり前の対応ができないのか、そしてその裏にある現場の構造的な課題について整理します。

 

🤔 なぜ「PHPのバージョンアップ」で終わらないのか?

今回の脆弱性は、PHP 5.4から8.5という非常に広い範囲に影響を与えます。そして、ここが最大のネックなのですが、すでにサポートが終了(EOL)しているPHP 8.1以前のバージョンには、公式からのパッチが提供されません。

つまり、古いPHPを使っている環境は「修正版の8.2以降」へ一気にジャンプアップ(メジャー/マイナーバージョンアップ)する必要があります。

しかし、古いPHPで動いているシステムをそのまま最新のPHP 8.2/8.3に持っていくと、以下のような問題が確実に発生します。

・ 非推奨(Deprecated)機能・廃止関数の影響で画面が真っ白になる
・ 古いフレームワークやWordPressプラグインが動作しなくなる
・ データベースの接続ドライバや仕様変更に伴う挙動不審

結果として、「ただPHPのバイナリを差し替える」だけでは済まず、アプリケーションコードの大規模な改修と全機能の再テストが必要になります。

 

🤔 バージョンアップできないシステムは、ただの「延命」である

「コードを直せばいいじゃないか」と思うかもしれませんが、現場ではそれができません。

・ 仕様書がなく、当時の開発者もいない(誰も全体像を把握していない)
・「現状動いているから触るな」という経営陣・決裁者の圧力
・ 売上を生み出さない基盤改修に予算が出ない

このようにして、「修正費用が出せないから」「壊れるのが怖いから」とバージョンアップを先送りにしてきたシステムが、今回のPHP2Shellのような致命的な脆弱性に晒された瞬間、手も足も出なくなるのです。

WAF(Web Application Firewall)の設定調整や、一時的な設定変更で攻撃を防ごうとする現場もありますが、これはあくまで「時間稼ぎの応急処置」にすぎません。

バージョンアップに追従できないシステムは、ソフトウェアとしての寿命をすでに迎えており、「技術的負債の返済を先送りして延命しているだけ」というのが残酷な現実です。

 

🤔 開発者・エンジニアが取るべき姿勢

今回の騒動から私たちが学ぶべきなのは、「脆弱性が出たときにどう対処するか」だけではありません。「そもそもバージョンアップを前提としたアーキテクチャや保守体制をどう維持するか」という点です。

・ フレームワークや言語のマイナーアップデートを日々の運用に組み込む
・ EOL(サポート終了)を迎える前に、計画的なマイグレーション予算を確保する
・「動いているから触らない」ではなく、「触り続けられる状態(テストコードやCI/CD)を保つ」

これらができていないシステムは、どれだけ強固なセキュリティ製品を前段に置いたとしても、いつか必ず破綻します。

 

🤔 まとめ

「PHPのバージョンを上げたら終わりじゃないの?」という疑問に対する答えは、「理屈の上ではその通り。でも、上げられないシステムがあまりにも多すぎるから大騒ぎになっている」です。

セキュリティの脆弱性問題は、単なる技術の問題ではなく、これまでのシステム運用・投資判断のツケが可視化された結果とも言えます。

「そもそも、アナウンス済みのさまざまな脆弱性を放置したままだったよな?」

自社で管理しているシステムや関わっているプロダクトがあるなら、今回の件を機に「いつでも言語バージョンを上げられる状態になっているか?」を改めて見直してみるのが良いかもしれません。

👉 PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35 fix two critical vulnerabilities that can allow unauthenticated remote code execution. All PHP users are strongly encouraged to update immediately.


iPhone・Android・PC 間でデータを共有するなら、Google Keep が便利

 

🧑🏻‍💻 iPhone・Android・PC でデータを送りたい

スマートフォンとPCの間で、ちょっとしたデータを送りたいことがあります。

例えば、


iPhone → Android
Android → iPhone
iPhone → Windows PC
Android → Mac
PC → iPhone
PC → Android

といったケースです。

同じOSなら、AirDropやQuick Share など便利な方法があります。

しかし、iPhone・Android・Windows・Mac が混在すると、少し面倒です。

そこで便利なのが、Google Keep です。

 

🧑🏻‍💻 Google KeepならOSを気にしなくていい

Google Keep は、Android だけのアプリではありません。

スマートフォンだけでなく、PCのブラウザからも利用できます。


          Google Keep
               │
   ┌───────────┼───────────┐
   │           │           │
Android      iPhone      PC / Mac
   │           │           │
   └───────────┼───────────┘
               │
         Google Account

つまり、Google Keepを共有場所にすることで、OSの違いをあまり意識せずに済みます。

 

🧑🏻‍💻 Googleアカウントがあればいい

Google Keep の大きなメリットは、Google アカウントを使えることです。

例えば PC で調べた URL をスマートフォンに送りたいとします。


PC
 │
 │ URLをKeepに保存
 ▼
Google Keep
 │
 │ 同期
 ▼
iPhone / Android

スマートフォン側でGoogle Keepを開けば、そのURLを確認できます。

逆に、


iPhone
   │
   │ テキストを保存
   ▼
Google Keep
   │
   │ 同期
   ▼
Windows PC

のように、スマートフォンからPCへ送ることもできます。

 

🧑🏻‍💻 「自分の端末間」で使うのが簡単

実は、Google Keep の便利さは自分の複数端末をつなぐ用途で特に分かりやすいです。

例えば、


       ┌──── iPhone
       │
       ├──── Android
Google ┤
Keep   ├──── Mac
       │
       └──── Windows PC

すべて同じ Google アカウントでログインしておけば、Keep のメモを各端末から確認できます。

「自分の iPhone から自分の Mac へ URL を送りたい」といった用途なら、かなり手軽です。

 

🧑🏻‍💻 他の人とも共有できる

Google Keep は、自分の端末間だけでなく他の人との共有にも使えます。

共有相手の Google アカウントを指定すれば、同じメモを共同編集できます。


      Google Keep
      /         \
     /           \
自分の Google    相手の Google
  Account         Account
     │               │
  Android          iPhone
     │               │
    PC              PC

そのため、

・ 自分の Android → 家族の iPhone
・ 自分の Mac → 同僚の Windows
・ 自分の iPhone → 自分の PC

のような使い方もできます。

 

🧑🏻‍💻 「Google アカウントは今どき持っている」がポイント

iPhone ユーザーでも Google アカウントを持っている人は珍しくありません。

Android ユーザーなら、Google アカウントを利用しているケースはさらに多いでしょう。

PC でもブラウザから Google Keep を利用できます。

そのため、特別なアプリや専用の転送ケーブルを用意しなくても、OSの違いを超えて情報を共有できます。

 

🧑🏻‍💻 まとめ

iPhone・Android・Windows・Mac が混在する環境では、データ共有の方法に悩みがちです。

Google Keepなら、

・ Android
・ iPhone / iPad
・ Windows
・ Mac
・ Chromebook

などから同じデータにアクセスできます。

特に、

・ URL
・ テキスト
・ メモ
・ チェックリスト
・ 小さな画像

のようなちょっとしたデータを別の端末へ渡す用途に向いています。

「iPhone か Android か」ではなく、「Google Keep を共有場所にする」と考えると、端末間のデータ共有がかなりシンプルになります。


Google推奨 MVVM と MVI の違い

Android の UI アーキテクチャでは、MVVM と MVI のどちらを選ぶべきかという議論がよくあります。

ただし、現在の Google の公式ガイドを見ると、重要なのは「MVVM か MVI か」という名前ではありません。

Google が推奨している中心的な考え方は、UDF(Unidirectional Data Flow), UI State・State Holder, ViewModel です。

その上で、MVI はより厳密に「すべての入力を Action として集約し、Reducer で State を生成する」方向へ進めたアーキテクチャとして考えることができます。

 

🧑🏻‍💻 1. Input Processing — Event の処理

MVVM: ViewModel のメソッドを直接呼び出す

UI から発生したイベントごとに、対応する ViewModel のメソッドを呼び出します。


Button(
    onClick = { viewModel.onSubmitClicked() }
)


fun onSubmitClicked() {
    viewModelScope.launch {
        ...
    }
}

このスタイルでは、UI イベントと ViewModel の API が直接対応します。

Google の Compose アーキテクチャガイドでも、UI イベントを ViewModel の onSignIn() のようなメソッドで処理する例が示されています。

MVI: Single Entry Point

MVI では、UI からの入力を Intent や Action に変換し、1つの入口に集約することがあります。


Button(
    onClick = { viewModel.onIntent(UserIntent.Submit) }
)


sealed interface UserIntent {
    data object Submit : UserIntent
    data object Refresh : UserIntent
}

fun onIntent(intent: UserIntent) {
    ...
}

これにより、UI → ViewModel の入力経路を1本化できます。

すべての入力を同じ形式で扱えるため、ログやテスト、イベント追跡を統一しやすくなります。

 

🧑🏻‍💻 2. State Reduction — State の更新

MVVM: ViewModel の処理から State を更新する

MVVM では、ViewModel の各メソッドが非同期処理を行い、その結果を UiState に反映する構成が一般的です。


fun loadData() {
    viewModelScope.launch {
        val result = repository.getData()

        _uiState.update {
            it.copy(data = result)
        }
    }
}

この場合、State を変更する場所は ViewModel 内の複数のメソッドに分散する可能性があります。

その代わり、コードが比較的シンプルで、処理の流れを直接追いやすいというメリットがあります。

MVI: Reducer で State を計算する

MVI では、現在の State と Action から次の State を計算する reduce を中心に据えることができます。


fun reduce(
    oldState: UiState,
    action: Action
): UiState =
    when (action) {
        is Action.DataLoaded ->
            oldState.copy(data = action.data)

        is Action.Loading ->
            oldState.copy(isLoading = true)
    }

ここで重要なのは、


Current State + Action
          ↓
       Reducer
          ↓
      Next State

という形にすることです。

Reducer を純粋関数として設計すれば、同じ State と Action から常に同じ State を生成するという性質を持たせられます。

そのため、State 遷移のテストやログの再現が容易になります。

 

🧑🏻‍💻 3. Output Processing — UI 更新と Side Effect

MVVM: State を中心に UI を更新する

Google の現在の Android アーキテクチャガイドは、ViewModel が UI State を生成し、UI がそれを購読する UDF を推奨しています。


val uiState: StateFlow<UiState> =
    _uiState.asStateFlow()

val state by viewModel.uiState
    .collectAsStateWithLifecycle()

例えば Snackbar を UI State に含める実装では、UI が State の変化を監視して表示できます。


LaunchedEffect(state.userMessage) {
    state.userMessage?.let {
        snackbarHostState.showSnackbar(it)
        viewModel.onMessageShown()
    }
}

この方式では、


ViewModel
    ↓
 UiState
    ↓
   UI

という一本の State Flow に寄せることができます。

なお、「一度だけ実行したいイベントを State に入れるべきか」については、現在の Google のガイドラインでは以前より明確に State 中心へ寄っています。

Google は ViewModel から UI へ直接イベントを送るのではなく、イベントの結果を UI State に反映することを推奨しています。

MVI: State と Effect を分離する

一方、MVI では永続的な UI State と、一度だけ実行したい Side Effect を分離する設計も一般的です。


private val _effect = Channel<UiEffect>()

val effect = _effect.receiveAsFlow()

val state by viewModel.state
    .collectAsStateWithLifecycle()

UI 側では、


LaunchedEffect(Unit) {
    viewModel.effect.collect { effect ->
        when (effect) {
            is UiEffect.ShowSnackbar ->
                snackbarHostState.showSnackbar(
                    effect.message
                )
        }
    }
}

のように State と Effect を別々に処理します。


      ┌───────────┐
      │ ViewModel │
      └─────┬─────┘
            │
   ┌────────┴────────┐
   ↓                 ↓
UiState           UiEffect
   ↓                 ↓
  UI            Side Effect

この設計のメリットは、「画面を表現する State」と「UI に一度だけ何かをさせる Effect」の責務を明確に分離できることです。

一方で、Channel や SharedFlow、Effect の lifecycle や delivery guarantee まで設計する必要があり、複雑さも増えます。

 

🧑🏻‍💻 4. MVVM と MVI の違いを整理する

 

🧑🏻‍💻 5. 「MVVM vs MVI」ではなく「どこまで厳密にするか」

ここで重要なのは、MVVM と MVI は完全に対立するものではないということです。


              UDF
               │
     ┌─────────┴─────────┐
     ↓                   ↓
   MVVM                  MVI
     │                   │
ViewModel          Intent / Action
     │                   │
UI State             Reducer
     │                   │
     └─────────┬─────────┘
               ↓
              UI

Google の公式ガイドが推奨しているのは、MVI という名前ではなく、UDF によって UI State を一方向に流し、ViewModel などの State Holder がユーザーイベントを処理する構造です。

そのため、ViewModel に複数のイベントハンドラを持たせること自体が「悪い MVVM」なのではありません。

むしろ Google の公式サンプルでも、onSignIn() のようなイベントハンドラを ViewModel に公開する形が使われています。

MVI はそこからさらに一歩進めて、

UI Event


   ↓
Intent / Action
   ↓
Reducer
   ↓
UiState
   ↓
UI

という形に統一することで、状態遷移そのものをアーキテクチャの中心に置く考え方だと言えます。

 

🧑🏻‍💻 6. どちらを選ぶべきか

シンプルな画面

- MVVM + UDF が適しています。
- ViewModel のメソッドを直接呼び出すだけで十分です。

状態遷移が複雑な画面

- MVI の考え方が有効です。
- Action と Reducer を明確に分離することで、状態遷移を追いやすくできます。

大規模チーム

- MVI のような厳密なルールが、チーム内の実装パターンを統一する助けになる場合があります。
- ただし、Reducer や Intent、Effect を増やすこと自体が目的になってはいけません。

重要なのはアーキテクチャの名前ではありません。

- State をどこに置くのか。
- 誰が State を変更するのか。
- Event をどこで処理するのか。
- UI に何を State として公開するのか。
- UI Effect をどのように扱うのか。

これらを明確にすることが、MVVM と MVI を選択する本質です。

 

🧑🏻‍💻 まとめ

MVVM は、少ないコードで UDF を実現しやすい実用的な選択肢です。

MVI は、Intent → Action → Reducer → State という流れを厳密に定義することで、State 遷移の予測可能性を高めます。

Google の現在の公式 Android ガイドは、MVI を推奨しているわけではなく、UDF + UI State + State Holder + ViewModel を中心に説明しています。

したがって、現代の Android では、


MVVM
  ↓
UDF + State
  ↓
必要なら MVI 的な Reducer / Action を導入

という考え方のほうが、「MVVM か MVI か」という二択よりも実際の設計に近いと言えるでしょう。

 

🧑🏻‍💻 参考


2026年8月31日 API レベル36+ の件

もう疲れました。

Google PlayアプリのターゲットAPIレベル要件
2026年8月31日から開始:

・ Google Playに提出される新規アプリおよびアプリのアップデートは、Android 16(APIレベル36)以降をターゲットにする必要があります。ただし、Wear OSおよびAndroid Automotive OSアプリはAndroid 15(APIレベル35)以降をターゲットに、Android TVおよびAndroid XRアプリはAndroid 14(APIレベル34)以降をターゲットにする必要があります。

・既存のアプリは、アプリのターゲットAPIレベルよりも高いAndroid OSを実行しているデバイスで新規ユーザーが引き続き利用できるようにするには、Android 15(APIレベル35)以上をターゲットにする必要があります。Android 14(APIレベル34)以下をターゲットとするアプリ(Wear OSおよびAndroid TV向けのAndroid 13(APIレベル33)以下、Android XR向け、Android Automotive OS向けのAndroid 12(APIレベル31)以下を含む)は、アプリのターゲットAPIレベルと同じかそれ以下のAndroid OSを実行しているデバイスでのみ利用可能です。

アプリのアップデートにさらに時間が必要な場合は、2026年11月1日まで延長を申請できます 。アプリの延長申請フォームは、今年後半にPlay Consoleからアクセスできるようになります。

👉 Target API level requirements for Google Play apps - Play Console Help

実質のユーザ利用の端末OSレベルが API 36+ で 22.3% ということなのですが。

アプリ開発側だけが辛い感じで。

みなさんは、どう思ってますか。