<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Node on Tarragon</title><link>https://tarrragon.github.io/blog/tags/node/</link><description>Recent content in Node on Tarragon</description><generator>Hugo -- gohugo.io</generator><language>zh-TW</language><copyright>Tarragon (CC BY 4.0)</copyright><lastBuildDate>Fri, 26 Jun 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tarrragon.github.io/blog/tags/node/index.xml" rel="self" type="application/rss+xml"/><item><title>Runtime 版本升級</title><link>https://tarrragon.github.io/blog/infra/upgrade/runtime-version-upgrade/</link><pubDate>Fri, 26 Jun 2026 00:00:00 +0000</pubDate><guid>https://tarrragon.github.io/blog/infra/upgrade/runtime-version-upgrade/</guid><description>&lt;p>Runtime 版本升級改變的是既有程式碼的執行環境。程式碼是針對某個版本的行為寫的——函式存不存在、預設值是什麼、型別檢查嚴不嚴格——新版本可能移除函式、改變預設行為、引入更嚴格的型別系統。升級的工作量不在「切換版本」這個動作本身（多數環境只需要改一個設定），而在「讓既有程式碼在新版本下行為正確」的驗證與修正。&lt;/p>
&lt;p>本篇以 PHP 為主要範例（legacy 升級最常見的情境），Node.js 和 Python 的對應工具在各段併列。&lt;/p>
&lt;h2 id="相容性評估">相容性評估&lt;/h2>
&lt;p>升級前要先知道「現有程式碼跟新版本有多少不相容」。不相容的類型分四種：&lt;/p>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>類型&lt;/th>
 &lt;th>範例（PHP 7→8）&lt;/th>
 &lt;th>影響&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>移除的函式&lt;/td>
 &lt;td>&lt;code>each()&lt;/code>、&lt;code>create_function()&lt;/code>、&lt;code>mysql_*&lt;/code> 系列&lt;/td>
 &lt;td>呼叫直接 fatal error&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>改變的預設行為&lt;/td>
 &lt;td>&lt;code>error_reporting&lt;/code> 預設含 &lt;code>E_DEPRECATED&lt;/code>、字串比較更嚴格&lt;/td>
 &lt;td>行為靜默改變、不一定報錯&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>更嚴格的型別&lt;/td>
 &lt;td>內部函式的參數型別檢查從警告升級為 TypeError&lt;/td>
 &lt;td>之前能跑的呼叫現在拋例外&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>擴充模組可用性&lt;/td>
 &lt;td>&lt;code>json&lt;/code> 從可選變內建、&lt;code>mcrypt&lt;/code> 已移除&lt;/td>
 &lt;td>部分功能無法使用&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="php-相容性掃描">PHP 相容性掃描&lt;/h3>
&lt;p>PHPCompatibility 是 PHP_CodeSniffer 的規則集，可以自動掃描程式碼裡哪些寫法在目標版本不相容：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 安裝&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">composer global require phpcompatibility/php-compatibility
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1"># 掃描：目標版本 8.0&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">phpcs --standard&lt;span class="o">=&lt;/span>PHPCompatibility &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="se">&lt;/span> --runtime-set testVersion 8.0 &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">&lt;span class="se">&lt;/span> --extensions&lt;span class="o">=&lt;/span>php &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">8&lt;/span>&lt;span class="cl">&lt;span class="se">&lt;/span> -p &lt;span class="se">\
&lt;/span>&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">9&lt;/span>&lt;span class="cl">&lt;span class="se">&lt;/span> src/&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>掃描結果會列出每一處不相容的位置、原因和嚴重度。常見的命中包括：&lt;/p>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-text" data-lang="text">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">FILE: src/legacy/Database.php
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">----------------------------------------------------------------------
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">FOUND 3 ERRORS:
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl"> 42 | ERROR | Function mysql_connect() is removed since PHP 7.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl"> 89 | ERROR | Function each() is removed since PHP 8.0
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">156 | ERROR | Curly brace access syntax is deprecated since PHP 7.4
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">----------------------------------------------------------------------&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>php -l&lt;/code> 可以做基本的語法檢查，但它只抓語法錯誤、抓不到 deprecated 函式和行為變更。PHPCompatibility 掃描的覆蓋面更廣。&lt;/p>
&lt;h3 id="php-升級的高頻修改項">PHP 升級的高頻修改項&lt;/h3>
&lt;table>
 &lt;thead>
 &lt;tr>
 &lt;th>項目&lt;/th>
 &lt;th>PHP 5.6→7.x&lt;/th>
 &lt;th>PHP 7.x→8.x&lt;/th>
 &lt;/tr>
 &lt;/thead>
 &lt;tbody>
 &lt;tr>
 &lt;td>資料庫連線&lt;/td>
 &lt;td>&lt;code>mysql_*&lt;/code> → &lt;code>mysqli_*&lt;/code> 或 PDO&lt;/td>
 &lt;td>—&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>陣列遍歷&lt;/td>
 &lt;td>—&lt;/td>
 &lt;td>&lt;code>each()&lt;/code> → &lt;code>foreach&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>字串存取&lt;/td>
 &lt;td>—&lt;/td>
 &lt;td>&lt;code>$str{0}&lt;/code> → &lt;code>$str[0]&lt;/code>&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>錯誤處理&lt;/td>
 &lt;td>&lt;code>set_error_handler&lt;/code> 行為變更&lt;/td>
 &lt;td>內部函式 TypeError 取代 warning&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>建構函式&lt;/td>
 &lt;td>同名建構函式 deprecated&lt;/td>
 &lt;td>同名建構函式 removed&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>正則表達式&lt;/td>
 &lt;td>&lt;code>ereg_*&lt;/code> → &lt;code>preg_*&lt;/code>&lt;/td>
 &lt;td>—&lt;/td>
 &lt;/tr>
 &lt;tr>
 &lt;td>加密&lt;/td>
 &lt;td>&lt;code>mcrypt_*&lt;/code> → &lt;code>openssl_*&lt;/code> 或 sodium&lt;/td>
 &lt;td>—&lt;/td>
 &lt;/tr>
 &lt;/tbody>
&lt;/table>
&lt;h3 id="nodejs-相容性掃描">Node.js 相容性掃描&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># 用 nvm 切換版本後跑測試&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">nvm install &lt;span class="m">20&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">nvm use &lt;span class="m">20&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">npm &lt;span class="nb">test&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">&lt;span class="c1"># 檢查 package.json 的 engines 欄位&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">7&lt;/span>&lt;span class="cl">cat package.json &lt;span class="p">|&lt;/span> jq &lt;span class="s1">&amp;#39;.engines&amp;#39;&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Node.js 的 breaking change 集中在 V8 引擎行為（&lt;code>Buffer&lt;/code> 建構式、&lt;code>fs&lt;/code> 的 callback 簽章）和原生模組的 ABI 相容性。如果專案用了原生模組（&lt;code>node-gyp&lt;/code> 編譯的），版本升級後要重新 &lt;code>npm rebuild&lt;/code>。&lt;/p>
&lt;h3 id="python-相容性掃描">Python 相容性掃描&lt;/h3>





&lt;div class="highlight">&lt;pre tabindex="0" class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="line">&lt;span class="ln">1&lt;/span>&lt;span class="cl">&lt;span class="c1"># Python 2→3：用 2to3 掃描&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">2&lt;/span>&lt;span class="cl">2to3 --no-diffs -w src/
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">3&lt;/span>&lt;span class="cl">
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">4&lt;/span>&lt;span class="cl">&lt;span class="c1"># Python 3.x 小版本：用 pyupgrade&lt;/span>
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">5&lt;/span>&lt;span class="cl">pip install pyupgrade
&lt;/span>&lt;/span>&lt;span class="line">&lt;span class="ln">6&lt;/span>&lt;span class="cl">pyupgrade --py310-plus src/**/*.py&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Python 2→3 的修改量通常很大（print 語法、unicode 處理、dict 方法），是接近重寫等級的升級。Python 3.x 之間的升級相對溫和，主要是 deprecation 移除和 typing 語法的演進。&lt;/p></description><content:encoded><![CDATA[<p>Runtime 版本升級改變的是既有程式碼的執行環境。程式碼是針對某個版本的行為寫的——函式存不存在、預設值是什麼、型別檢查嚴不嚴格——新版本可能移除函式、改變預設行為、引入更嚴格的型別系統。升級的工作量不在「切換版本」這個動作本身（多數環境只需要改一個設定），而在「讓既有程式碼在新版本下行為正確」的驗證與修正。</p>
<p>本篇以 PHP 為主要範例（legacy 升級最常見的情境），Node.js 和 Python 的對應工具在各段併列。</p>
<h2 id="相容性評估">相容性評估</h2>
<p>升級前要先知道「現有程式碼跟新版本有多少不相容」。不相容的類型分四種：</p>
<table>
  <thead>
      <tr>
          <th>類型</th>
          <th>範例（PHP 7→8）</th>
          <th>影響</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>移除的函式</td>
          <td><code>each()</code>、<code>create_function()</code>、<code>mysql_*</code> 系列</td>
          <td>呼叫直接 fatal error</td>
      </tr>
      <tr>
          <td>改變的預設行為</td>
          <td><code>error_reporting</code> 預設含 <code>E_DEPRECATED</code>、字串比較更嚴格</td>
          <td>行為靜默改變、不一定報錯</td>
      </tr>
      <tr>
          <td>更嚴格的型別</td>
          <td>內部函式的參數型別檢查從警告升級為 TypeError</td>
          <td>之前能跑的呼叫現在拋例外</td>
      </tr>
      <tr>
          <td>擴充模組可用性</td>
          <td><code>json</code> 從可選變內建、<code>mcrypt</code> 已移除</td>
          <td>部分功能無法使用</td>
      </tr>
  </tbody>
</table>
<h3 id="php-相容性掃描">PHP 相容性掃描</h3>
<p>PHPCompatibility 是 PHP_CodeSniffer 的規則集，可以自動掃描程式碼裡哪些寫法在目標版本不相容：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 安裝</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">composer global require phpcompatibility/php-compatibility
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 掃描：目標版本 8.0</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">phpcs --standard<span class="o">=</span>PHPCompatibility <span class="se">\
</span></span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="se"></span>  --runtime-set testVersion 8.0 <span class="se">\
</span></span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="se"></span>  --extensions<span class="o">=</span>php <span class="se">\
</span></span></span><span class="line"><span class="ln">8</span><span class="cl"><span class="se"></span>  -p <span class="se">\
</span></span></span><span class="line"><span class="ln">9</span><span class="cl"><span class="se"></span>  src/</span></span></code></pre></div><p>掃描結果會列出每一處不相容的位置、原因和嚴重度。常見的命中包括：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="ln">1</span><span class="cl">FILE: src/legacy/Database.php
</span></span><span class="line"><span class="ln">2</span><span class="cl">----------------------------------------------------------------------
</span></span><span class="line"><span class="ln">3</span><span class="cl">FOUND 3 ERRORS:
</span></span><span class="line"><span class="ln">4</span><span class="cl"> 42 | ERROR | Function mysql_connect() is removed since PHP 7.0
</span></span><span class="line"><span class="ln">5</span><span class="cl"> 89 | ERROR | Function each() is removed since PHP 8.0
</span></span><span class="line"><span class="ln">6</span><span class="cl">156 | ERROR | Curly brace access syntax is deprecated since PHP 7.4
</span></span><span class="line"><span class="ln">7</span><span class="cl">----------------------------------------------------------------------</span></span></code></pre></div><p><code>php -l</code> 可以做基本的語法檢查，但它只抓語法錯誤、抓不到 deprecated 函式和行為變更。PHPCompatibility 掃描的覆蓋面更廣。</p>
<h3 id="php-升級的高頻修改項">PHP 升級的高頻修改項</h3>
<table>
  <thead>
      <tr>
          <th>項目</th>
          <th>PHP 5.6→7.x</th>
          <th>PHP 7.x→8.x</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>資料庫連線</td>
          <td><code>mysql_*</code> → <code>mysqli_*</code> 或 PDO</td>
          <td>—</td>
      </tr>
      <tr>
          <td>陣列遍歷</td>
          <td>—</td>
          <td><code>each()</code> → <code>foreach</code></td>
      </tr>
      <tr>
          <td>字串存取</td>
          <td>—</td>
          <td><code>$str{0}</code> → <code>$str[0]</code></td>
      </tr>
      <tr>
          <td>錯誤處理</td>
          <td><code>set_error_handler</code> 行為變更</td>
          <td>內部函式 TypeError 取代 warning</td>
      </tr>
      <tr>
          <td>建構函式</td>
          <td>同名建構函式 deprecated</td>
          <td>同名建構函式 removed</td>
      </tr>
      <tr>
          <td>正則表達式</td>
          <td><code>ereg_*</code> → <code>preg_*</code></td>
          <td>—</td>
      </tr>
      <tr>
          <td>加密</td>
          <td><code>mcrypt_*</code> → <code>openssl_*</code> 或 sodium</td>
          <td>—</td>
      </tr>
  </tbody>
</table>
<h3 id="nodejs-相容性掃描">Node.js 相容性掃描</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># 用 nvm 切換版本後跑測試</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">nvm install <span class="m">20</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">nvm use <span class="m">20</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">npm <span class="nb">test</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">
</span></span><span class="line"><span class="ln">6</span><span class="cl"><span class="c1"># 檢查 package.json 的 engines 欄位</span>
</span></span><span class="line"><span class="ln">7</span><span class="cl">cat package.json <span class="p">|</span> jq <span class="s1">&#39;.engines&#39;</span></span></span></code></pre></div><p>Node.js 的 breaking change 集中在 V8 引擎行為（<code>Buffer</code> 建構式、<code>fs</code> 的 callback 簽章）和原生模組的 ABI 相容性。如果專案用了原生模組（<code>node-gyp</code> 編譯的），版本升級後要重新 <code>npm rebuild</code>。</p>
<h3 id="python-相容性掃描">Python 相容性掃描</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># Python 2→3：用 2to3 掃描</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">2to3 --no-diffs -w src/
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># Python 3.x 小版本：用 pyupgrade</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">pip install pyupgrade
</span></span><span class="line"><span class="ln">6</span><span class="cl">pyupgrade --py310-plus src/**/*.py</span></span></code></pre></div><p>Python 2→3 的修改量通常很大（print 語法、unicode 處理、dict 方法），是接近重寫等級的升級。Python 3.x 之間的升級相對溫和，主要是 deprecation 移除和 typing 語法的演進。</p>
<h2 id="本地驗證">本地驗證</h2>
<p>相容性掃描找出的是靜態分析能偵測的不相容。執行期的行為變更（如字串比較規則改變、排序穩定性改變）只有跑起來才看得到。</p>
<h3 id="建立目標版本的本地環境">建立目標版本的本地環境</h3>
<p>用 Docker 建一個精確匹配目標版本的環境：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 2</span><span class="cl"><span class="w">  </span><span class="nt">app</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">php:8.2-apache</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 4</span><span class="cl"><span class="w">    </span><span class="nt">volumes</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 5</span><span class="cl"><span class="w">      </span>- <span class="l">./src:/var/www/html</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="w">    </span><span class="nt">ports</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="w">      </span>- <span class="s2">&#34;8080:80&#34;</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 8</span><span class="cl"><span class="w">  </span><span class="nt">db</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">mysql:8.0</span><span class="w">
</span></span></span><span class="line"><span class="ln">10</span><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="ln">11</span><span class="cl"><span class="w">      </span><span class="nt">MYSQL_ROOT_PASSWORD</span><span class="p">:</span><span class="w"> </span><span class="l">localdev</span><span class="w">
</span></span></span><span class="line"><span class="ln">12</span><span class="cl"><span class="w">      </span><span class="nt">MYSQL_DATABASE</span><span class="p">:</span><span class="w"> </span><span class="l">app</span></span></span></code></pre></div><p>如果不用 Docker，MAMP Pro 或 Laragon 可以切換 PHP 版本。關鍵是本地環境的 runtime 版本要跟升級目標完全一致——PHP 8.0 跟 8.2 之間也有差異。</p>
<h3 id="驗證策略">驗證策略</h3>
<p>有測試套件的專案跑測試套件。沒有測試套件的專案（legacy 專案的常態）按照這個優先序手動驗證：</p>
<ol>
<li><strong>首頁能載入</strong>：最基本的 smoke test，確認 PHP 不 fatal error</li>
<li><strong>登入流程</strong>：session 處理是版本升級最常出問題的區域</li>
<li><strong>資料庫操作</strong>：CRUD 的每一種至少各跑一次</li>
<li><strong>金流 / 第三方 API</strong>：callback URL 和 API 呼叫是否正常</li>
<li><strong>表單提交</strong>：file upload、驗證邏輯</li>
</ol>
<p>PHP 升級時把 <code>error_reporting</code> 開到最大：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1">// 開發環境設定（不要在 prod 開）
</span></span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="c1"></span><span class="nx">error_reporting</span><span class="p">(</span><span class="k">E_ALL</span><span class="p">);</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="nx">ini_set</span><span class="p">(</span><span class="s1">&#39;display_errors&#39;</span><span class="p">,</span> <span class="s1">&#39;1&#39;</span><span class="p">);</span></span></span></code></pre></div><p>所有 notice、warning、deprecation 都要修——它們在下一個版本可能升級為 error。</p>
<h3 id="第三方依賴相容性">第三方依賴相容性</h3>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># Composer：檢查哪些套件需要更新</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">composer outdated
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 檢查各套件是否支援目標 PHP 版本</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">composer why-not php 8.2</span></span></code></pre></div><p><code>composer why-not</code> 會列出哪些套件的 <code>require.php</code> 限制不允許目標版本。這些套件要先升級到支援新版本的版號，才能升 PHP。</p>
<p>如果某個套件已經不再維護且不支援新 PHP 版本，要評估替代方案或 fork 修改。這個評估的工作量可能佔整個升級的大部分時間。</p>
<h2 id="分批部署策略">分批部署策略</h2>
<h3 id="有獨立環境控制的情境vps--雲端">有獨立環境控制的情境（VPS / 雲端）</h3>
<p>最安全的策略是建一套平行環境跑新版本：</p>
<ol>
<li>用新 PHP 版本建一台新的 VM 或容器</li>
<li>部署相同的程式碼</li>
<li>匯入 prod 資料庫的副本</li>
<li>在新環境跑完整驗證</li>
<li>DNS 或 load balancer 切換流量到新環境</li>
<li>舊環境保留一段時間作為 rollback 目標</li>
</ol>
<p>rollback 是把流量切回舊環境。舊環境在確認新環境穩定之前不要關——保留期至少一週。</p>
<h3 id="面板管理主機無-ssh的情境">面板管理主機（無 SSH）的情境</h3>
<p>面板管理主機（cPanel / Plesk）的 PHP 版本切換通常是 per-domain 的設定：</p>
<ul>
<li><strong>cPanel</strong>：MultiPHP Manager，選域名 → 選 PHP 版本 → Apply</li>
<li><strong>Plesk</strong>：PHP Settings → PHP version 下拉選單</li>
</ul>
<p>切換是即時生效的，rollback 也是即時的（選回舊版本）。但沒有「平行環境驗證」的能力——除非主機商提供 staging subdomain 可以先測。</p>
<p>面板管理主機的升級策略：</p>
<ol>
<li>如果有 staging subdomain：先在 staging 切換版本、驗證、再切 prod</li>
<li>如果沒有：選流量最低的時段切換（如凌晨），切換後立刻驗證關鍵流程，出問題立刻切回</li>
<li>切換前備份（FTP mirror + DB dump），確認 rollback 路徑存在</li>
</ol>
<h3 id="wordpress--框架的版本矩陣">WordPress / 框架的版本矩陣</h3>
<p>WordPress 和主流框架有明確的 PHP 版本支援矩陣。升級 PHP 前要先確認框架版本是否支援目標 PHP 版本：</p>
<table>
  <thead>
      <tr>
          <th>框架</th>
          <th>查詢方式</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>WordPress</td>
          <td><a href="https://wordpress.org/about/requirements/">官方需求頁</a></td>
      </tr>
      <tr>
          <td>Laravel</td>
          <td>各版本 <code>composer.json</code> 的 <code>require.php</code></td>
      </tr>
      <tr>
          <td>Symfony</td>
          <td><a href="https://symfony.com/releases">Release and support</a> 頁面</td>
      </tr>
  </tbody>
</table>
<p>如果框架不支援目標 PHP 版本，要先升級框架。框架升級和 PHP 升級不要同時做——先升框架、驗證穩定、再升 PHP，每一步都有獨立的 rollback 點。</p>
<h2 id="常見的升級陷阱">常見的升級陷阱</h2>
<h3 id="session-序列化格式">Session 序列化格式</h3>
<p>PHP 的 session 序列化格式在某些版本之間有變更。版本切換後舊 session 檔案可能無法反序列化，使用者會被強制登出。處理方式：</p>
<ul>
<li>在維護窗口切換版本（使用者預期重新登入）</li>
<li>或在切換前清除所有 session 檔案</li>
</ul>
<h3 id="opcache-快取">opcache 快取</h3>
<p>PHP 的 opcache 會快取編譯後的 bytecode。版本切換後如果 opcache 沒清，可能用舊版本編譯的 bytecode 跑在新版本上。切換後的第一件事：</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="ln">1</span><span class="cl"><span class="c1"># CLI 方式清除（如果有 SSH）</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">php -r <span class="s2">&#34;opcache_reset();&#34;</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="c1"># 或重啟 PHP-FPM / Apache</span>
</span></span><span class="line"><span class="ln">5</span><span class="cl">systemctl restart php8.2-fpm</span></span></code></pre></div><h3 id="composer-的-php-版本鎖定">Composer 的 PHP 版本鎖定</h3>
<p><code>composer.lock</code> 裡的套件版本是根據當時的 PHP 版本解析的。PHP 版本變了之後，要重新 <code>composer update</code> 讓 Composer 用新版本重新解析依賴。但 <code>composer update</code> 可能升級其他套件——較安全的做法是 <code>composer update --lock</code> 只更新 lock file 的 metadata、不升級套件版本。</p>
<h3 id="隱性的行為變更">隱性的行為變更</h3>
<p>PHP 8.0 起，字串跟數字的比較規則改了（<code>0 == &quot;foo&quot;</code> 從 <code>true</code> 變 <code>false</code>）。這類變更不會報錯、不會拋例外，程式碼照跑但行為不同。靜態分析抓不到，只有業務邏輯測試能覆蓋。</p>
<p>如果沒有測試套件，至少在切換後的一週內密切監控錯誤日誌和業務指標（訂單數、登入數、API 錯誤率），用業務指標的異常作為行為變更的偵測手段。</p>
<h2 id="時程與管理層溝通">時程與管理層溝通</h2>
<table>
  <thead>
      <tr>
          <th>升級類型</th>
          <th>典型時程</th>
          <th>主要成本來源</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>PHP 小版本（8.0→8.2）</td>
          <td>2-5 天</td>
          <td>依賴更新 + 測試</td>
      </tr>
      <tr>
          <td>PHP 跨大版本（7.4→8.x）</td>
          <td>1-2 週</td>
          <td>函式替換 + 行為驗證</td>
      </tr>
      <tr>
          <td>PHP 跳代（5.6→8.x）</td>
          <td>4-8 週</td>
          <td>大量程式碼修改 + 框架升級</td>
      </tr>
      <tr>
          <td>Node.js 大版本</td>
          <td>3-5 天</td>
          <td>原生模組重編 + API 變更</td>
      </tr>
      <tr>
          <td>Python 2→3</td>
          <td>8-16 週</td>
          <td>接近重寫等級</td>
      </tr>
  </tbody>
</table>
<p>向管理層溝通時要說明：「升級 runtime 版本不只是在伺服器改一個設定。程式碼裡用到的函式和行為在新版本有不同的定義，需要逐一修改和驗證。時程取決於程式碼用了多少舊版本的專屬功能。」</p>
<p>成本參考：PHP 版本升級本身的工具和環境不花錢（PHPCompatibility 開源、Docker 免費、cPanel 版本切換內建）。成本全在工程師時間。</p>
<h2 id="跨分類引用">跨分類引用</h2>
<ul>
<li>→ <a href="/blog/infra/upgrade/upgrade-framework/" data-link-title="升級的共通操作框架" data-link-desc="任何環境或系統升級的四階段模型：差異評估、平行環境驗證、分批切換、退役舊環境，以及貫穿全程的升級紀律">升級的共通操作框架</a>：四階段模型（評估 → 平行環境 → 切換 → 退役）</li>
<li>→ <a href="/blog/infra/takeover/legacy-php-security-audit/" data-link-title="Legacy PHP 的安全盤點" data-link-desc="接手 legacy PHP 專案後的系統性安全審查：credential 掃描、PHP 版本風險、常見漏洞模式的 grep 偵測、.htaccess 防線、檔案權限、外部依賴與掃描工具">Legacy PHP 的安全盤點</a>：PHP 版本風險評估與漏洞掃描</li>
<li>→ <a href="/blog/infra/takeover/legacy-code-versioning-deployment/" data-link-title="程式碼版控與 FTP 部署紀律" data-link-desc="無 SSH 環境的 PHP 專案的程式碼怎麼從 FTP 拉回來建 Git repo、設定檔怎麼分離、FTP 部署怎麼建立可追蹤的流程、以及怎麼用 CI 取代手動上傳">程式碼版控與 FTP 部署紀律</a>：升級前的 Git 基準線與 rollback 策略</li>
</ul>
]]></content:encoded></item></channel></rss>